Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99a326ce2e | ||
|
|
3cce4477cb | ||
|
|
9e74ab28fb | ||
|
|
3b7a1556d1 | ||
|
|
f5b8717ad4 | ||
|
|
23e77d7204 | ||
|
|
27b5ca3c13 | ||
| 37a82ebd47 | |||
| c52ef617e9 | |||
| b7ec18cf21 | |||
| 15f09af83a | |||
| 326ddaa9af | |||
| ee1d705447 | |||
| 87fd77666a | |||
| c1154f93ae | |||
| 342fa394ca | |||
| b73a5950c7 | |||
| 918ad4e6bf | |||
| 0e5bc86455 | |||
| 57d0ad1a6c | |||
| d2ce86a5d5 | |||
| 7bb29b396b | |||
| dd1bb37886 | |||
| 32df7f142a | |||
| e48e687520 | |||
| 1d7c9e854b | |||
| 093d31725a | |||
| 8edc4885db | |||
| 4edbcc2386 | |||
| 51db120a1e | |||
| 95763ea9a6 | |||
| 2dc8511844 | |||
| 048c3eda30 | |||
| 2394ebe031 | |||
| 7808ecef97 | |||
| a4e884420b | |||
| 4402439ad4 | |||
| 11a49e9c58 | |||
| 5afde181d8 | |||
| 6f50a03c70 | |||
| e690f23c4e | |||
| bf231a39ad | |||
| 3864f40bac | |||
| d169f008f5 | |||
| 06414edeb4 | |||
| 76f143d44e | |||
| deea73b19d | |||
| 7c9e599150 | |||
| c951969812 | |||
| b680355fe9 | |||
| 46c8a6b3e1 | |||
| b9f1e224e3 | |||
| 2584abbd95 | |||
| f7979b5359 | |||
| d272e709e1 | |||
| 2609f783b2 | |||
| a534e6445b | |||
| b7dd32493b | |||
|
|
71534e7e06 | ||
|
|
d703187fae |
269
README.md
269
README.md
@@ -2,41 +2,127 @@
|
||||
|
||||
TeleportSuite ist ein umfassendes Teleport-Plugin für Paper/Spigot mit Homes, Warps, Portalen, Spawn-System, Savepoints, Back/Deathback, Teleportanfragen und BungeeCord-Unterstützung.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Homes mit konfigurierbaren Limits
|
||||
- Homes mit konfigurierbaren Limits pro Rang
|
||||
- Warps mit optionalen Berechtigungen
|
||||
- Portale für Server- oder Weltwechsel
|
||||
- Spawn, FirstSpawn und Deathback
|
||||
- Teleportanfragen mit Accept/Deny
|
||||
- Teleportanfragen (TPA) mit Accept/Deny
|
||||
- Back- und Cooldown-/Warmup-System
|
||||
- Teleport zu Koordinaten, Welten und Spielern
|
||||
- Entity-Transport
|
||||
- SQLite- und MySQL-Unterstützung
|
||||
- BungeeCord-Unterstützung für verteilte Servernetzwerke
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Paper oder Spigot ab Minecraft 1.20
|
||||
- Java 21 oder neuer
|
||||
- Optional: MySQL, falls du nicht SQLite verwenden möchtest
|
||||
- Optional: MySQL, falls mehrere Server auf dieselbe Datenbank zugreifen sollen
|
||||
- Optional: BungeeCord-Proxy, falls du mehrere Server vernetzt hast
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
1. Die fertige JAR-Datei in den Ordner plugins deines Servers kopieren.
|
||||
2. Den Server einmal starten, damit die Konfigurationsdatei und die Datenbank angelegt werden.
|
||||
3. Den Server stoppen und die Datei config.yml nach Bedarf anpassen.
|
||||
4. Den Server neu starten.
|
||||
### Einzelner Server (kein BungeeCord)
|
||||
|
||||
## Konfiguration
|
||||
1. Die JAR-Datei in den `plugins`-Ordner deines Servers kopieren.
|
||||
2. Server starten → Plugin legt `config.yml` und Datenbank automatisch an.
|
||||
3. Server stoppen und `config.yml` nach Bedarf anpassen.
|
||||
4. Server neu starten.
|
||||
|
||||
Die Datei config.yml wird beim ersten Start im Plugin-Ordner angelegt.
|
||||
---
|
||||
|
||||
### Datenbank
|
||||
### BungeeCord-Netzwerk ⚠️
|
||||
|
||||
> Wenn du mehrere Server mit BungeeCord verbunden hast und Spieler **serverübergreifend** teleportieren sollen, musst du das Plugin auf **jedem Spigot-Server UND auf dem BungeeCord-Proxy** installieren.
|
||||
|
||||
**Schritt-für-Schritt:**
|
||||
|
||||
**1. JAR auf jeden Spigot-Server kopieren**
|
||||
|
||||
Kopiere die `TeleportSuite.jar` in den `plugins`-Ordner von **jedem einzelnen** Spigot-Server in deinem Netzwerk (z.B. Lobby, Survival, Creative, ...).
|
||||
|
||||
**2. JAR auf den BungeeCord-Proxy kopieren**
|
||||
|
||||
Kopiere dieselbe `TeleportSuite.jar` auch in den `plugins`-Ordner deines **BungeeCord-Proxys**. Das Plugin erkennt automatisch ob es auf Spigot oder BungeeCord läuft.
|
||||
|
||||
**3. Jeden Server einmal starten**
|
||||
|
||||
Starte jeden Server einmal kurz, damit die `config.yml` angelegt wird. Dann stoppe ihn wieder.
|
||||
|
||||
**4. config.yml auf jedem Spigot-Server anpassen**
|
||||
|
||||
Öffne die `plugins/TeleportSuite/config.yml` auf **jedem Spigot-Server** und passe folgende Einstellungen an:
|
||||
|
||||
```yaml
|
||||
bungee:
|
||||
enabled: true # ← MUSS auf true stehen, sonst kein Cross-Server-Teleport!
|
||||
server-name: "survival" # ← Den Namen dieses Servers eintragen (siehe unten)
|
||||
```
|
||||
|
||||
> **Woher bekomme ich den `server-name`?**
|
||||
> Öffne die `config.yml` deines **BungeeCord-Proxys** (nicht des Spigot-Servers!).
|
||||
> Dort findest du einen Abschnitt wie diesen:
|
||||
> ```yaml
|
||||
> servers:
|
||||
> lobby:
|
||||
> address: localhost:25566
|
||||
> survival:
|
||||
> address: localhost:25567
|
||||
> creative:
|
||||
> address: localhost:25568
|
||||
> ```
|
||||
> Der `server-name` in der TeleportSuite-Config muss **exakt** dem Namen aus dieser Liste entsprechen.
|
||||
> Beispiel: Auf dem Survival-Server trägst du `server-name: "survival"` ein.
|
||||
> Auf dem Lobby-Server trägst du `server-name: "lobby"` ein. Und so weiter.
|
||||
|
||||
**5. Datenbank einrichten (empfohlen: MySQL)**
|
||||
|
||||
Damit alle Server auf dieselben Homes, Warps usw. zugreifen, solltest du MySQL verwenden und auf jedem Spigot-Server dieselben Zugangsdaten eintragen:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
type: sqlite
|
||||
type: mysql
|
||||
mysql:
|
||||
host: localhost # Adresse deines MySQL-Servers
|
||||
port: 3306
|
||||
database: teleportsuite
|
||||
username: root
|
||||
password: deinPasswort
|
||||
```
|
||||
|
||||
**6. Alle Server neu starten**
|
||||
|
||||
Starte jetzt alle Spigot-Server und den BungeeCord-Proxy neu. Wenn alles korrekt konfiguriert ist, siehst du beim Start des jeweiligen Spigot-Servers in der Konsole:
|
||||
|
||||
```
|
||||
[TeleportSuite] BungeeCord-Unterstuetzung aktiviert. Server: survival
|
||||
```
|
||||
|
||||
Falls du stattdessen diese Warnung siehst:
|
||||
|
||||
```
|
||||
[TeleportSuite] BungeeCord deaktiviert (bungee.enabled=false).
|
||||
```
|
||||
|
||||
→ Dann hast du in der `config.yml` dieses Servers `enabled: false` stehen gelassen. Auf `true` ändern und neu starten.
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die vollständige `config.yml` mit allen Optionen:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
type: sqlite # sqlite oder mysql
|
||||
mysql:
|
||||
host: localhost
|
||||
port: 3306
|
||||
@@ -46,140 +132,101 @@ database:
|
||||
pool-size: 10
|
||||
sqlite:
|
||||
file: teleportsuite.db
|
||||
```
|
||||
|
||||
- `sqlite` ist die Standard-Einstellung und benötigt keine zusätzliche Serverdatenbank.
|
||||
- `mysql` ist sinnvoll, wenn mehrere Server auf dieselben Teleport-Daten zugreifen sollen.
|
||||
|
||||
### BungeeCord
|
||||
|
||||
```yaml
|
||||
bungee:
|
||||
enabled: false
|
||||
server-name: "survival"
|
||||
```
|
||||
enabled: true
|
||||
server-name: "survival" # Name dieses Servers in der BungeeCord config.yml
|
||||
|
||||
- Aktiviere `enabled: true`, wenn das Plugin im BungeeCord-Setup genutzt werden soll.
|
||||
- `server-name` muss dem Namen des Servers im Netzwerk entsprechen.
|
||||
|
||||
### Teleport
|
||||
|
||||
```yaml
|
||||
teleport:
|
||||
delay: 3
|
||||
cooldown: 5
|
||||
warmup-cancel-on-move: true
|
||||
request-timeout: 60
|
||||
```
|
||||
delay: 3 # Wartezeit in Sekunden vor dem Teleport (0 = sofort)
|
||||
cooldown: 5 # Cooldown in Sekunden zwischen Teleports
|
||||
warmup-cancel-on-move: true # Teleport abbrechen wenn Spieler sich bewegt
|
||||
request-timeout: 60 # Sekunden bis eine TPA-Anfrage automatisch verfällt
|
||||
|
||||
- `delay` ist die Wartezeit vor einem Teleport.
|
||||
- `cooldown` verhindert zu häufige Teleports.
|
||||
- `warmup-cancel-on-move` bricht den Teleport ab, wenn sich der Spieler bewegt.
|
||||
- `request-timeout` legt fest, wie lange eine TPA-Anfrage gültig bleibt.
|
||||
|
||||
### Homes
|
||||
|
||||
```yaml
|
||||
homes:
|
||||
max-homes-default: 3
|
||||
max-homes-vip: 10
|
||||
max-homes-premium: 25
|
||||
```
|
||||
|
||||
### Warps
|
||||
|
||||
```yaml
|
||||
warps:
|
||||
allow-player-warps: false
|
||||
warp-permission-prefix: "teleportsuite.warp."
|
||||
```
|
||||
|
||||
### Portale
|
||||
|
||||
```yaml
|
||||
portals:
|
||||
check-interval: 5
|
||||
particle-effect: true
|
||||
```
|
||||
|
||||
### Spawn
|
||||
|
||||
```yaml
|
||||
spawn:
|
||||
first-join-teleport: true
|
||||
death-respawn-to-spawn: false
|
||||
```
|
||||
|
||||
### Nachrichten
|
||||
|
||||
Alle Nachrichten unterstützen `&`-Farbcodes und Platzhalter wie `{player}`, `{name}`, `{seconds}` und `{max}`.
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Beschreibung | Permission |
|
||||
|---|---|---|
|
||||
| /tp <player> | Teleportiere zu einem Spieler | teleportsuite.tp |
|
||||
| /tphere <player> | Teleportiere einen Spieler zu dir | teleportsuite.tphere |
|
||||
| /tpa <player> | Sende eine Teleportanfrage | teleportsuite.tpa |
|
||||
| /tpaccept | Akzeptiere eine Teleportanfrage | teleportsuite.tpaccept |
|
||||
| /tpdeny | Lehne eine Teleportanfrage ab | teleportsuite.tpdeny |
|
||||
| /back | Teleportiere zur letzten Position | teleportsuite.back |
|
||||
| /deathback | Teleportiere zum letzten Todesort | teleportsuite.deathback |
|
||||
| /sethome [name] | Setze ein Home | teleportsuite.sethome |
|
||||
| /home [name] | Teleportiere zu einem Home | teleportsuite.home |
|
||||
| /delhome <name> | Lösche ein Home | teleportsuite.delhome |
|
||||
| /homes | Liste alle Homes auf | teleportsuite.home |
|
||||
| /setwarp <name> | Setze einen Warp | teleportsuite.setwarp |
|
||||
| /warp <name> | Teleportiere zu einem Warp | teleportsuite.warp |
|
||||
| /delwarp <name> | Lösche einen Warp | teleportsuite.delwarp |
|
||||
| /warps | Liste alle Warps auf | teleportsuite.warp |
|
||||
| /setportal <name> <target-server/world> | Erstelle ein Portal | teleportsuite.setportal |
|
||||
| /delportal <name> | Lösche ein Portal | teleportsuite.delportal |
|
||||
| /portals | Liste alle Portale auf | teleportsuite.portals |
|
||||
| /setsavepoint [name] | Setze einen Savepoint | teleportsuite.savepoint |
|
||||
| /savepoint [name] | Teleportiere zu einem Savepoint | teleportsuite.savepoint |
|
||||
| /setspawn | Setze den Spawn | teleportsuite.setspawn |
|
||||
| /spawn | Teleportiere zum Spawn | teleportsuite.spawn |
|
||||
| /setfirstspawn | Setze den FirstSpawn | teleportsuite.setfirstspawn |
|
||||
| /tppos <x> <y> <z> [world] | Teleportiere zu Koordinaten | teleportsuite.tppos |
|
||||
| /tpall | Teleportiere alle Spieler zu dir | teleportsuite.tpall |
|
||||
| /tpworld <world> | Teleportiere in eine Welt | teleportsuite.tpworld |
|
||||
| /entitytransport <entity-id> <player/world> | Transportiere ein Entity | teleportsuite.entitytransport |
|
||||
| `/tp <spieler>` | Teleportiere zu einem Spieler | `teleportsuite.tp` |
|
||||
| `/tphere <spieler>` | Teleportiere einen Spieler zu dir | `teleportsuite.tphere` |
|
||||
| `/tpa <spieler>` | Sende eine Teleportanfrage | `teleportsuite.tpa` |
|
||||
| `/tpaccept` | Akzeptiere eine Teleportanfrage | `teleportsuite.tpaccept` |
|
||||
| `/tpdeny` | Lehne eine Teleportanfrage ab | `teleportsuite.tpdeny` |
|
||||
| `/back` | Teleportiere zur letzten Position | `teleportsuite.back` |
|
||||
| `/deathback` | Teleportiere zum letzten Todesort | `teleportsuite.deathback` |
|
||||
| `/sethome [name]` | Setze ein Home | `teleportsuite.sethome` |
|
||||
| `/home [name]` | Teleportiere zu einem Home | `teleportsuite.home` |
|
||||
| `/delhome <name>` | Lösche ein Home | `teleportsuite.delhome` |
|
||||
| `/homes` | Liste alle Homes auf | `teleportsuite.home` |
|
||||
| `/setwarp <name>` | Setze einen Warp | `teleportsuite.setwarp` |
|
||||
| `/warp <name>` | Teleportiere zu einem Warp | `teleportsuite.warp` |
|
||||
| `/delwarp <name>` | Lösche einen Warp | `teleportsuite.delwarp` |
|
||||
| `/warps` | Liste alle Warps auf | `teleportsuite.warp` |
|
||||
| `/setportal <name> <server/welt>` | Erstelle ein Portal | `teleportsuite.setportal` |
|
||||
| `/delportal <name>` | Lösche ein Portal | `teleportsuite.delportal` |
|
||||
| `/portals` | Liste alle Portale auf | `teleportsuite.portals` |
|
||||
| `/setsavepoint [name]` | Setze einen Savepoint | `teleportsuite.savepoint` |
|
||||
| `/savepoint [name]` | Teleportiere zu einem Savepoint | `teleportsuite.savepoint` |
|
||||
| `/setspawn` | Setze den Spawn | `teleportsuite.setspawn` |
|
||||
| `/spawn` | Teleportiere zum Spawn | `teleportsuite.spawn` |
|
||||
| `/setfirstspawn` | Setze den FirstSpawn (für neue Spieler) | `teleportsuite.setfirstspawn` |
|
||||
| `/tppos <x> <y> <z> [welt]` | Teleportiere zu Koordinaten | `teleportsuite.tppos` |
|
||||
| `/tpall` | Teleportiere alle Spieler zu dir | `teleportsuite.tpall` |
|
||||
| `/tpworld <welt>` | Teleportiere in eine Welt | `teleportsuite.tpworld` |
|
||||
| `/entitytransport <id> <spieler/welt>` | Transportiere ein Entity | `teleportsuite.entitytransport` |
|
||||
|
||||
---
|
||||
|
||||
## Permissions
|
||||
|
||||
### Sammelrechte
|
||||
| Permission | Beschreibung |
|
||||
|---|---|
|
||||
| `teleportsuite.*` | Alle Rechte des Plugins |
|
||||
| `teleportsuite.home.unlimited` | Unbegrenzte Homes (Standard: nur OPs) |
|
||||
| `teleportsuite.nodelay` | Kein Teleport-Warmup |
|
||||
| `teleportsuite.nocooldown` | Kein Teleport-Cooldown |
|
||||
| `teleportsuite.admin` | Andere Spieler teleportieren (`/tp <von> <zu>`) |
|
||||
|
||||
- `teleportsuite.*` - Alle Rechte des Plugins
|
||||
- `teleportsuite.home.unlimited` - Unbegrenzte Homes, standardmäßig nur für OPs
|
||||
Alle weiteren Einzelrechte entsprechen dem jeweiligen Command-Namen, z.B. `teleportsuite.tp`, `teleportsuite.home`, `teleportsuite.warp` usw.
|
||||
|
||||
### Wichtige Einzelrechte
|
||||
---
|
||||
|
||||
- `teleportsuite.tp`
|
||||
- `teleportsuite.tphere`
|
||||
- `teleportsuite.tpa`
|
||||
- `teleportsuite.tpaccept`
|
||||
- `teleportsuite.tpdeny`
|
||||
- `teleportsuite.back`
|
||||
- `teleportsuite.deathback`
|
||||
- `teleportsuite.sethome`
|
||||
- `teleportsuite.home`
|
||||
- `teleportsuite.delhome`
|
||||
- `teleportsuite.setwarp`
|
||||
- `teleportsuite.warp`
|
||||
- `teleportsuite.delwarp`
|
||||
- `teleportsuite.setportal`
|
||||
- `teleportsuite.delportal`
|
||||
- `teleportsuite.portals`
|
||||
- `teleportsuite.savepoint`
|
||||
- `teleportsuite.setspawn`
|
||||
- `teleportsuite.spawn`
|
||||
- `teleportsuite.setfirstspawn`
|
||||
- `teleportsuite.tppos`
|
||||
- `teleportsuite.tpall`
|
||||
- `teleportsuite.tpworld`
|
||||
- `teleportsuite.entitytransport`
|
||||
## Häufige Probleme
|
||||
|
||||
**„Spieler XY nicht gefunden" beim serverübergreifenden `/tp`**
|
||||
→ `bungee.enabled` in der `config.yml` steht auf `false`. Auf `true` setzen und neu starten.
|
||||
|
||||
**Spieler landet am Spawn statt beim Zielspieler**
|
||||
→ `server-name` in der `config.yml` stimmt nicht mit dem Namen in der BungeeCord `config.yml` überein.
|
||||
|
||||
**TPA-Nachrichten kommen nicht an**
|
||||
→ Das Plugin läuft nicht auf dem BungeeCord-Proxy. Die JAR muss auch in den `plugins`-Ordner des Proxys.
|
||||
|
||||
**Homes/Warps sind auf anderen Servern nicht verfügbar**
|
||||
→ Alle Server müssen dieselbe MySQL-Datenbank verwenden. SQLite funktioniert nur lokal auf einem Server.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
Wenn du Probleme, Wünsche oder Fehlerberichte hast, eröffne ein Issue auf GitHub und füge wenn möglich den vollständigen Server-Log sowie deine `config.yml` bei.
|
||||
Probleme, Wünsche oder Fehlerberichte bitte als Issue auf GitHub melden. Füge wenn möglich den vollständigen Server-Log sowie deine `config.yml` bei.
|
||||
2
pom.xml
2
pom.xml
@@ -7,7 +7,7 @@
|
||||
|
||||
<groupId>de.teleportsuite</groupId>
|
||||
<artifactId>TeleportSuite</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.4</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -42,9 +42,19 @@ public class TeleportSuite extends JavaPlugin {
|
||||
savePointManager = new SavePointManager(this);
|
||||
|
||||
if (configManager.isBungeeEnabled()) {
|
||||
String serverName = configManager.getServerName();
|
||||
if (serverName == null || serverName.isBlank() || serverName.equals("survival")) {
|
||||
getLogger().warning("==================================================");
|
||||
getLogger().warning("BUNGEE: bungee.server-name in config.yml ist noch");
|
||||
getLogger().warning("auf dem Default-Wert 'survival'. Bitte auf den");
|
||||
getLogger().warning("tatsaechlichen BungeeCord-Servernamen aendern!");
|
||||
getLogger().warning("==================================================");
|
||||
}
|
||||
bungeeMessenger = new BungeeMessenger(this);
|
||||
bungeeMessenger.register();
|
||||
getLogger().info("BungeeCord-Unterstuetzung aktiviert.");
|
||||
getLogger().info("BungeeCord-Unterstuetzung aktiviert. Server: " + serverName);
|
||||
} else {
|
||||
getLogger().warning("BungeeCord deaktiviert (bungee.enabled=false). Cross-Server-Teleport nicht verfuegbar.");
|
||||
}
|
||||
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.teleportsuite.bungee;
|
||||
|
||||
import de.teleportsuite.bungee.listener.PlayerConnectionListener;
|
||||
import de.teleportsuite.bungee.manager.TeleportMessageListener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
|
||||
public class TeleportSuiteBungee extends Plugin {
|
||||
|
||||
public static final String CHANNEL_TO_BUNGEE = "teleportsuite:tobungee";
|
||||
public static final String CHANNEL_TO_SPIGOT = "teleportsuite:tospigot";
|
||||
|
||||
private static TeleportSuiteBungee instance;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
PluginManager pm = getProxy().getPluginManager();
|
||||
|
||||
getProxy().registerChannel(CHANNEL_TO_BUNGEE);
|
||||
getProxy().registerChannel(CHANNEL_TO_SPIGOT);
|
||||
|
||||
pm.registerListener(this, new TeleportMessageListener(this));
|
||||
pm.registerListener(this, new PlayerConnectionListener(this));
|
||||
|
||||
getLogger().info("TeleportSuite-Bungee aktiviert.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
getProxy().getScheduler().cancel(this);
|
||||
getLogger().info("TeleportSuite-Bungee deaktiviert.");
|
||||
}
|
||||
|
||||
public static TeleportSuiteBungee getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package de.teleportsuite.bungee.listener;
|
||||
|
||||
import de.teleportsuite.bungee.TeleportSuiteBungee;
|
||||
import de.teleportsuite.bungee.manager.PendingTpa;
|
||||
import de.teleportsuite.bungee.manager.TeleportMessageListener;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Cleans up pending TPA requests when a player disconnects.
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
private final TeleportSuiteBungee plugin;
|
||||
|
||||
public PlayerConnectionListener(TeleportSuiteBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(PlayerDisconnectEvent event) {
|
||||
String name = event.getPlayer().getName();
|
||||
|
||||
// Player was a requester
|
||||
PendingTpa tpa = TeleportMessageListener.getPendingTpa().remove(name);
|
||||
if (tpa != null) {
|
||||
notifyOther(tpa.getToName(), name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Player was a target — find the requester
|
||||
TeleportMessageListener.getPendingTpa().entrySet().removeIf(entry -> {
|
||||
if (entry.getValue().getToName().equalsIgnoreCase(name)) {
|
||||
notifyOther(entry.getValue().getFromName(), name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyOther(String otherName, String disconnectedName) {
|
||||
ProxiedPlayer other = plugin.getProxy().getPlayer(otherName);
|
||||
if (other == null || other.getServer() == null) return;
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("SEND_MSG");
|
||||
out.writeUTF(otherName);
|
||||
out.writeUTF("§cDeine TPA-Anfrage wurde abgebrochen, da §6" + disconnectedName + " §cdas Netzwerk verlassen hat.");
|
||||
other.getServer().sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Disconnect-Benachrichtigung fehlgeschlagen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.teleportsuite.bungee.manager;
|
||||
|
||||
/**
|
||||
* Represents a pending cross-server TPA request.
|
||||
*/
|
||||
public class PendingTpa {
|
||||
|
||||
public enum Type { TPTO, TPHERE }
|
||||
|
||||
private final String fromName;
|
||||
private final String toName;
|
||||
private final Type type;
|
||||
|
||||
public PendingTpa(String fromName, String toName, Type type) {
|
||||
this.fromName = fromName;
|
||||
this.toName = toName;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getFromName() { return fromName; }
|
||||
public String getToName() { return toName; }
|
||||
public Type getType() { return type; }
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package de.teleportsuite.bungee.manager;
|
||||
|
||||
import de.teleportsuite.bungee.TeleportSuiteBungee;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Receives plugin messages from Spigot servers on CHANNEL_TO_BUNGEE,
|
||||
* performs the cross-server operation (connect + forward), and sends
|
||||
* results back on CHANNEL_TO_SPIGOT.
|
||||
*
|
||||
* Payload identifiers (first UTF written by Spigot):
|
||||
*
|
||||
* TP_PLAYER – teleport sender to a named target player
|
||||
* TP_POS – teleport player to fixed coordinates on a server
|
||||
* TP_HERE – pull a remote player to the sender's location
|
||||
* TP_ALL – pull all network players to sender
|
||||
* TPA_REQUEST – forward "you have a TPA request" to target
|
||||
* TPA_ACCEPT – accept pending TPA; trigger the actual teleport
|
||||
* TPA_DENY – deny pending TPA; notify requester
|
||||
* TPA_CANCEL – requester cancels their own pending request
|
||||
* TPA_EXPIRED – timeout: notify both parties
|
||||
* SEND_MSG – route a chat message to a player on any server
|
||||
*/
|
||||
public class TeleportMessageListener implements Listener {
|
||||
|
||||
// fromName → pending request
|
||||
private static final Map<String, PendingTpa> pendingTpa = new HashMap<>();
|
||||
|
||||
private final TeleportSuiteBungee plugin;
|
||||
|
||||
public TeleportMessageListener(TeleportSuiteBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static Map<String, PendingTpa> getPendingTpa() {
|
||||
return pendingTpa;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Incoming messages from Spigot
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent event) throws IOException {
|
||||
if (event.isCancelled()) return;
|
||||
if (!(event.getSender() instanceof Server)) return;
|
||||
if (!event.getTag().equals(TeleportSuiteBungee.CHANNEL_TO_BUNGEE)) return;
|
||||
|
||||
DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()));
|
||||
String task = in.readUTF();
|
||||
|
||||
switch (task) {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// /tp <target> → teleport sender directly to target player
|
||||
// ------------------------------------------------------------------
|
||||
case "TP_PLAYER": {
|
||||
String senderName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
|
||||
ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
|
||||
ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);
|
||||
|
||||
if (sender == null) return;
|
||||
if (target == null) {
|
||||
sendMsg(sender, "§cSpieler §6" + targetName + " §cnicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
teleportPlayerToPlayer(sender, target, 50);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// /tp <from> <to> → teleport another player to a target player
|
||||
// ------------------------------------------------------------------
|
||||
case "TP_PLAYER_TO_PLAYER": {
|
||||
String requesterName = in.readUTF();
|
||||
String fromName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
|
||||
ProxiedPlayer requester = plugin.getProxy().getPlayer(requesterName);
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
|
||||
ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);
|
||||
|
||||
if (requester == null) return;
|
||||
if (from == null || target == null) {
|
||||
sendMsg(requester, "§cEin Spieler wurde nicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
teleportPlayerToPlayer(from, target, 50);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// /tphere <target> → pull target to sender's location
|
||||
// ------------------------------------------------------------------
|
||||
case "TP_HERE": {
|
||||
String senderName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
|
||||
ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
|
||||
ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);
|
||||
|
||||
if (sender == null) return;
|
||||
if (target == null) {
|
||||
sendMsg(sender, "§cSpieler §6" + targetName + " §cnicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
teleportPlayerToPlayer(target, sender, 50);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// /tpall → pull every player on the network to sender
|
||||
// ------------------------------------------------------------------
|
||||
case "TP_ALL": {
|
||||
String senderName = in.readUTF();
|
||||
ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
|
||||
if (sender == null) return;
|
||||
|
||||
for (ProxiedPlayer p : plugin.getProxy().getPlayers()) {
|
||||
if (!p.getName().equals(senderName)) {
|
||||
teleportPlayerToPlayer(p, sender, 50);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// /home, /warp, /spawn, /back, /savepoint, /portal, /tppos …
|
||||
// Spigot already knows the coordinates; we just switch the server
|
||||
// and tell that server where to put the player.
|
||||
// ------------------------------------------------------------------
|
||||
case "TP_POS": {
|
||||
String playerName = in.readUTF();
|
||||
String server = in.readUTF();
|
||||
String world = in.readUTF();
|
||||
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
|
||||
float yaw = in.readFloat(), pitch = in.readFloat();
|
||||
|
||||
ProxiedPlayer player = plugin.getProxy().getPlayer(playerName);
|
||||
if (player == null) return;
|
||||
|
||||
teleportPlayerToPosition(player, server, world, x, y, z, yaw, pitch, 100);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TPA: requester asks target for a teleport
|
||||
// ------------------------------------------------------------------
|
||||
case "TPA_REQUEST": {
|
||||
String fromName = in.readUTF();
|
||||
String toName = in.readUTF();
|
||||
String type = in.readUTF(); // TPTO or TPHERE
|
||||
String msgToTarget = in.readUTF();
|
||||
String msgToRequester = in.readUTF();
|
||||
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(toName);
|
||||
|
||||
if (from == null) return;
|
||||
if (to == null) {
|
||||
sendMsg(from, "§cSpieler §6" + toName + " §cnicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for existing pending request
|
||||
if (pendingTpa.containsKey(fromName)) {
|
||||
sendMsg(from, "§cDu hast bereits eine offene Anfrage.");
|
||||
return;
|
||||
}
|
||||
|
||||
pendingTpa.put(fromName, new PendingTpa(fromName, toName, PendingTpa.Type.valueOf(type)));
|
||||
|
||||
// Notify both players — via their respective servers
|
||||
sendMsgToServer(from.getServer().getInfo(), fromName, msgToRequester);
|
||||
sendMsgToServer(to.getServer().getInfo(), toName, msgToTarget);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TPA: target accepts → trigger the actual teleport
|
||||
// ------------------------------------------------------------------
|
||||
case "TPA_ACCEPT": {
|
||||
String toName = in.readUTF(); // player who accepted
|
||||
String fromName = in.readUTF(); // original requester (may be "nu" = any)
|
||||
String errMsg = in.readUTF();
|
||||
|
||||
PendingTpa tpa = resolvePending(fromName, toName);
|
||||
if (tpa == null) {
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(toName);
|
||||
if (to != null) sendMsg(to, errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingTpa.remove(tpa.getFromName());
|
||||
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(tpa.getFromName());
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(tpa.getToName());
|
||||
|
||||
if (from == null || to == null) return;
|
||||
|
||||
if (tpa.getType() == PendingTpa.Type.TPTO) {
|
||||
teleportPlayerToPlayer(from, to, 50);
|
||||
} else {
|
||||
teleportPlayerToPlayer(to, from, 50);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TPA: target denies → notify requester
|
||||
// ------------------------------------------------------------------
|
||||
case "TPA_DENY": {
|
||||
String toName = in.readUTF();
|
||||
String fromName = in.readUTF();
|
||||
String msg = in.readUTF();
|
||||
|
||||
PendingTpa tpa = resolvePending(fromName, toName);
|
||||
if (tpa == null) return;
|
||||
|
||||
pendingTpa.remove(tpa.getFromName());
|
||||
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(tpa.getFromName());
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(tpa.getToName());
|
||||
|
||||
if (from != null) sendMsg(from, msg);
|
||||
if (to != null) sendMsg(to, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TPA: requester cancels their own request
|
||||
// ------------------------------------------------------------------
|
||||
case "TPA_CANCEL": {
|
||||
String fromName = in.readUTF();
|
||||
String msgFrom = in.readUTF();
|
||||
String msgTo = in.readUTF();
|
||||
|
||||
PendingTpa tpa = pendingTpa.remove(fromName);
|
||||
if (tpa == null) return;
|
||||
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(tpa.getToName());
|
||||
|
||||
if (from != null) sendMsg(from, msgFrom);
|
||||
if (to != null) sendMsg(to, msgTo);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TPA: timeout — remove pending and notify both sides
|
||||
// ------------------------------------------------------------------
|
||||
case "TPA_EXPIRED": {
|
||||
String fromName = in.readUTF();
|
||||
String toName = in.readUTF();
|
||||
String msgFrom = in.readUTF();
|
||||
String msgTo = in.readUTF();
|
||||
|
||||
pendingTpa.remove(fromName);
|
||||
|
||||
ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
|
||||
ProxiedPlayer to = plugin.getProxy().getPlayer(toName);
|
||||
|
||||
if (from != null) sendMsg(from, msgFrom);
|
||||
if (to != null) sendMsg(to, msgTo);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Generic cross-server message routing
|
||||
// ------------------------------------------------------------------
|
||||
case "SEND_MSG": {
|
||||
String targetName = in.readUTF();
|
||||
String message = in.readUTF();
|
||||
ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);
|
||||
if (target != null) sendMsg(target, message);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
plugin.getLogger().warning("Unbekannter Task: " + task);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Core teleport helpers — the BTM way
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Moves {@code sender} to {@code target}'s current server (if needed),
|
||||
* then after a short delay tells the target server to do the final
|
||||
* player.teleport(target) call via TP_PLAYERTOPLAYER.
|
||||
*
|
||||
* This is done entirely on the Bungee side:
|
||||
* 1. sender.connect(target.server) — BungeeCord switches the server
|
||||
* 2. delay ms later: server.sendData(TP_PLAYERTOPLAYER, ...)
|
||||
* → Spigot polls until both players are on the same server and teleports
|
||||
*/
|
||||
private void teleportPlayerToPlayer(ProxiedPlayer sender, ProxiedPlayer target, int delayMs) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
||||
if (sender == null || target == null) return;
|
||||
if (sender.getServer() == null || target.getServer() == null) return;
|
||||
|
||||
// Switch server if needed
|
||||
if (!sender.getServer().getInfo().getName()
|
||||
.equals(target.getServer().getInfo().getName())) {
|
||||
sender.connect(target.getServer().getInfo());
|
||||
}
|
||||
|
||||
// Tell the target server to teleport sender → target
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("TP_PLAYERTOPLAYER");
|
||||
out.writeUTF(sender.getName());
|
||||
out.writeUTF(target.getName());
|
||||
target.getServer().sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("TP_PLAYERTOPLAYER Fehler: " + e.getMessage());
|
||||
}
|
||||
}, delayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves {@code player} to {@code server}, then after a delay sends
|
||||
* TP_PLAYERTOPOSITION so Spigot teleports them to exact coordinates.
|
||||
*/
|
||||
private void teleportPlayerToPosition(ProxiedPlayer player, String server,
|
||||
String world, double x, double y, double z,
|
||||
float yaw, float pitch, int delayMs) {
|
||||
if (!plugin.getProxy().getServers().containsKey(server)) {
|
||||
sendMsg(player, "§cServer §6" + server + " §cnicht gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch server if needed
|
||||
if (!player.getServer().getInfo().getName().equals(server)) {
|
||||
player.connect(plugin.getProxy().getServerInfo(server));
|
||||
}
|
||||
|
||||
// After delay, tell the (now correct) server where to put the player
|
||||
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
||||
if (player.getServer() == null) return;
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("TP_PLAYERTOPOSITION");
|
||||
out.writeUTF(player.getName());
|
||||
out.writeUTF(world);
|
||||
out.writeDouble(x);
|
||||
out.writeDouble(y);
|
||||
out.writeDouble(z);
|
||||
out.writeFloat(yaw);
|
||||
out.writeFloat(pitch);
|
||||
player.getServer().sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("TP_PLAYERTOPOSITION Fehler: " + e.getMessage());
|
||||
}
|
||||
}, delayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Resolves which pending TPA entry matches an accept/deny. */
|
||||
private PendingTpa resolvePending(String fromName, String toName) {
|
||||
// Exact match
|
||||
if (!fromName.equals("nu") && pendingTpa.containsKey(fromName)) {
|
||||
return pendingTpa.get(fromName);
|
||||
}
|
||||
// Find by target name only
|
||||
for (PendingTpa tpa : pendingTpa.values()) {
|
||||
if (tpa.getToName().equalsIgnoreCase(toName)) {
|
||||
return tpa;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Sends a formatted chat message directly to a ProxiedPlayer. */
|
||||
private void sendMsg(ProxiedPlayer player, String message) {
|
||||
player.sendMessage(net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a chat message to a named player via their Spigot server.
|
||||
* The Spigot side handles the actual sendMessage() call.
|
||||
*/
|
||||
private void sendMsgToServer(net.md_5.bungee.api.config.ServerInfo server,
|
||||
String playerName, String message) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("SEND_MSG");
|
||||
out.writeUTF(playerName);
|
||||
out.writeUTF(message);
|
||||
server.sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("SEND_MSG Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,510 +1,285 @@
|
||||
package de.teleportsuite.bungeemessaging;
|
||||
|
||||
import de.teleportsuite.TeleportSuite;
|
||||
import de.teleportsuite.models.TeleportLocation;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles BungeeCord Plugin Messaging.
|
||||
* Sends players to other servers and transmits target coordinates
|
||||
* so a receiving TeleportSuite instance can teleport them on arrival.
|
||||
* Spigot-seitiger Kanal zum TeleportSuite-Bungee-Plugin.
|
||||
*
|
||||
* Channel "teleportsuite:tp" payload format (DataOutputStream):
|
||||
* String targetPlayer
|
||||
* String world
|
||||
* Double x, y, z
|
||||
* Float yaw, pitch
|
||||
* Prinzip (identisch zu BTM):
|
||||
* Spigot schickt Anfragen auf CHANNEL_TO_BUNGEE.
|
||||
* BungeeCord erledigt connect() + routing und schickt Befehle auf CHANNEL_TO_SPIGOT.
|
||||
* Spigot führt den finalen player.teleport() aus.
|
||||
*
|
||||
* Payloads Spigot → Bungee (CHANNEL_TO_BUNGEE):
|
||||
* TP_PLAYER senderName, targetName
|
||||
* TP_PLAYER_TO_PLAYER requesterName, fromName, toName
|
||||
* TP_HERE senderName, targetName
|
||||
* TP_ALL senderName
|
||||
* TP_POS playerName, server, world, x, y, z, yaw, pitch
|
||||
* TPA_REQUEST fromName, toName, type, msgToTarget, msgToRequester
|
||||
* TPA_ACCEPT toName, fromName, errMsg
|
||||
* TPA_DENY toName, fromName, msg
|
||||
* TPA_CANCEL fromName, msgFrom, msgTo
|
||||
* TPA_EXPIRED fromName, toName, msgFrom, msgTo
|
||||
* SEND_MSG targetName, message
|
||||
*
|
||||
* Payloads Bungee → Spigot (CHANNEL_TO_SPIGOT):
|
||||
* TP_PLAYERTOPLAYER senderName, targetName
|
||||
* TP_PLAYERTOPOSITION playerName, world, x, y, z, yaw, pitch
|
||||
* SEND_MSG playerName, message
|
||||
*/
|
||||
public class BungeeMessenger implements PluginMessageListener {
|
||||
|
||||
private static final String BUNGEE_CHANNEL = "BungeeCord";
|
||||
private static final String TS_CHANNEL = "teleportsuite:tp";
|
||||
private final TeleportSuite plugin;
|
||||
private final Map<String, List<Consumer<String>>> pendingServerLookups = new HashMap<>();
|
||||
private final Map<String, List<Consumer<List<String>>>> pendingPlayerListLookups = new HashMap<>();
|
||||
public static final String CHANNEL_TO_BUNGEE = "teleportsuite:tobungee";
|
||||
public static final String CHANNEL_TO_SPIGOT = "teleportsuite:tospigot";
|
||||
|
||||
public BungeeMessenger(TeleportSuite plugin) { this.plugin = plugin; }
|
||||
private final TeleportSuite plugin;
|
||||
|
||||
public BungeeMessenger(TeleportSuite plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL);
|
||||
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, TS_CHANNEL);
|
||||
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this);
|
||||
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, TS_CHANNEL, this);
|
||||
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL_TO_BUNGEE);
|
||||
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL_TO_SPIGOT, this);
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin);
|
||||
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin);
|
||||
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_TO_BUNGEE);
|
||||
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_TO_SPIGOT, this);
|
||||
}
|
||||
|
||||
public void requestPlayerServer(Player sender, String targetPlayer, Consumer<String> callback) {
|
||||
if (sender == null || !sender.isOnline()) {
|
||||
callback.accept(null);
|
||||
return;
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// Outgoing helpers (Spigot → Bungee)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
String key = targetPlayer.toLowerCase(Locale.ROOT);
|
||||
pendingServerLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
|
||||
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("GetPlayerServer");
|
||||
out.writeUTF(targetPlayer);
|
||||
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("GetPlayerServer Fehler: " + e.getMessage());
|
||||
flushServerLookupCallbacks(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestPlayerList(Player sender, String serverScope, Consumer<List<String>> callback) {
|
||||
if (sender == null || !sender.isOnline()) {
|
||||
callback.accept(List.of());
|
||||
return;
|
||||
}
|
||||
|
||||
String scope = serverScope == null || serverScope.isBlank() ? "ALL" : serverScope;
|
||||
String key = scope.toUpperCase(Locale.ROOT);
|
||||
pendingPlayerListLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
|
||||
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("PlayerList");
|
||||
out.writeUTF(scope);
|
||||
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("PlayerList Fehler: " + e.getMessage());
|
||||
flushPlayerListCallbacks(key, List.of());
|
||||
}
|
||||
}
|
||||
|
||||
public void teleportAllPlayersToLocalPlayer(Player requester) {
|
||||
String requesterName = requester.getName();
|
||||
requestPlayerList(requester, "ALL", players -> {
|
||||
if (players == null || players.isEmpty()) {
|
||||
requester.sendMessage("§cKeine Spieler gefunden.");
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (String playerName : players) {
|
||||
if (playerName == null || playerName.isBlank()) continue;
|
||||
if (playerName.equalsIgnoreCase(requesterName)) continue;
|
||||
if (!seen.add(playerName.toLowerCase(Locale.ROOT))) continue;
|
||||
teleportPlayerToLocalPlayer(requester, playerName, requesterName);
|
||||
}
|
||||
|
||||
requester.sendMessage("§aAlle verfügbaren Spieler im Netzwerk werden zu dir teleportiert.");
|
||||
});
|
||||
}
|
||||
|
||||
public void teleportToPlayer(Player requester, String targetPlayerName) {
|
||||
requestPlayerServer(requester, targetPlayerName, server -> {
|
||||
if (server == null || server.isBlank()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
|
||||
return;
|
||||
}
|
||||
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
if (server.equalsIgnoreCase(localServer)) {
|
||||
Player target = plugin.getServer().getPlayerExact(targetPlayerName);
|
||||
if (target == null || !target.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
|
||||
return;
|
||||
}
|
||||
plugin.getTeleportManager().teleport(
|
||||
requester,
|
||||
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sendPlayerToPlayerPayload(requester, requester.getName(), targetPlayerName, server);
|
||||
connectPlayerOnly(requester, server);
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||
});
|
||||
}
|
||||
|
||||
public void teleportPlayerToLocalPlayer(Player requester, String moverPlayerName, String localTargetPlayerName) {
|
||||
requestPlayerServer(requester, moverPlayerName, moverServer -> {
|
||||
if (moverServer == null || moverServer.isBlank()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
|
||||
return;
|
||||
}
|
||||
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
if (moverServer.equalsIgnoreCase(localServer)) {
|
||||
Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
|
||||
Player target = plugin.getServer().getPlayerExact(localTargetPlayerName);
|
||||
if (mover == null || !mover.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
|
||||
return;
|
||||
}
|
||||
if (target == null || !target.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", localTargetPlayerName));
|
||||
return;
|
||||
}
|
||||
plugin.getTeleportManager().teleport(
|
||||
mover,
|
||||
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sendPlayerToPlayerPayload(requester, moverPlayerName, localTargetPlayerName, localServer);
|
||||
connectOtherPlayer(requester, moverPlayerName, localServer);
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||
});
|
||||
}
|
||||
|
||||
public void teleportAnyPlayerToAnyPlayer(Player requester, String moverPlayerName, String targetPlayerName) {
|
||||
requestPlayerServer(requester, targetPlayerName, targetServer -> {
|
||||
if (targetServer == null || targetServer.isBlank()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
|
||||
return;
|
||||
}
|
||||
|
||||
requestPlayerServer(requester, moverPlayerName, moverServer -> {
|
||||
if (moverServer == null || moverServer.isBlank()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
|
||||
return;
|
||||
}
|
||||
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
if (moverServer.equalsIgnoreCase(localServer) && targetServer.equalsIgnoreCase(localServer)) {
|
||||
Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
|
||||
Player target = plugin.getServer().getPlayerExact(targetPlayerName);
|
||||
if (mover == null || !mover.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
|
||||
return;
|
||||
}
|
||||
if (target == null || !target.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
|
||||
return;
|
||||
}
|
||||
plugin.getTeleportManager().teleport(
|
||||
mover,
|
||||
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sendPlayerToPlayerPayload(requester, moverPlayerName, targetPlayerName, targetServer);
|
||||
if (!moverServer.equalsIgnoreCase(targetServer)) {
|
||||
connectOtherPlayer(requester, moverPlayerName, targetServer);
|
||||
}
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void sendTpaRequestToPlayer(Player sender, String targetPlayerName) {
|
||||
sendForwardToPlayer(sender, targetPlayerName, out -> {
|
||||
out.writeUTF("TPA_REQUEST");
|
||||
/** /tp <target> – Absender teleportiert sich zum Zielspieler */
|
||||
public void teleportToPlayer(Player sender, String targetName) {
|
||||
send(sender, out -> {
|
||||
out.writeUTF("TP_PLAYER");
|
||||
out.writeUTF(sender.getName());
|
||||
out.writeUTF(targetPlayerName);
|
||||
out.writeUTF(targetName);
|
||||
});
|
||||
}
|
||||
|
||||
public void sendTpaAcceptToPlayer(Player sender, String requesterName) {
|
||||
sendForwardToPlayer(sender, requesterName, out -> {
|
||||
out.writeUTF("TPA_ACCEPT");
|
||||
out.writeUTF(requesterName);
|
||||
out.writeUTF(sender.getName());
|
||||
/** /tp <from> <to> – Admin teleportiert einen Spieler zu einem anderen */
|
||||
public void teleportPlayerToPlayer(Player requester, String fromName, String targetName) {
|
||||
send(requester, out -> {
|
||||
out.writeUTF("TP_PLAYER_TO_PLAYER");
|
||||
out.writeUTF(requester.getName());
|
||||
out.writeUTF(fromName);
|
||||
out.writeUTF(targetName);
|
||||
});
|
||||
}
|
||||
|
||||
public void sendTpaDenyToPlayer(Player sender, String requesterName) {
|
||||
sendForwardToPlayer(sender, requesterName, out -> {
|
||||
out.writeUTF("TPA_DENY");
|
||||
out.writeUTF(requesterName);
|
||||
/** /tphere <target> – Zielspieler wird zu Absender geholt */
|
||||
public void teleportHere(Player sender, String targetName) {
|
||||
send(sender, out -> {
|
||||
out.writeUTF("TP_HERE");
|
||||
out.writeUTF(sender.getName());
|
||||
out.writeUTF(targetName);
|
||||
});
|
||||
}
|
||||
|
||||
public void sendTpaExpiredToPlayer(Player sender, String targetPlayerName) {
|
||||
sendForwardToPlayer(sender, targetPlayerName, out -> {
|
||||
out.writeUTF("TPA_EXPIRED");
|
||||
/** /tpall – Alle Netzwerkspieler zum Absender teleportieren */
|
||||
public void teleportAll(Player sender) {
|
||||
send(sender, out -> {
|
||||
out.writeUTF("TP_ALL");
|
||||
out.writeUTF(sender.getName());
|
||||
out.writeUTF(targetPlayerName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a player to another BungeeCord server.
|
||||
* Also sends a plugin message so the target server knows where to teleport the player.
|
||||
* Home / Warp / Spawn / Back / SavePoint / Portal / TpPos:
|
||||
* Koordinaten sind bekannt → Bungee wechselt den Server und Spigot
|
||||
* setzt den Spieler direkt an die exakten Koordinaten.
|
||||
*/
|
||||
public void connectToServer(Player player, String server, String world, double x, double y, double z, float yaw, float pitch) {
|
||||
// 1) Notify the target server about the pending teleport
|
||||
sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);
|
||||
|
||||
// 2) Switch server via BungeeCord
|
||||
connectPlayerOnly(player, server);
|
||||
}
|
||||
|
||||
private void connectPlayerOnly(Player player, String server) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("Connect");
|
||||
out.writeUTF(server);
|
||||
player.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("BungeeCord Connect Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectOtherPlayer(Player anchor, String playerName, String server) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("ConnectOther");
|
||||
out.writeUTF(playerName);
|
||||
out.writeUTF(server);
|
||||
anchor.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("BungeeCord ConnectOther Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTeleportPayload(Player sender, String targetPlayer, String server, String world,
|
||||
public void teleportToPosition(Player player, String server, String world,
|
||||
double x, double y, double z, float yaw, float pitch) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("Forward");
|
||||
send(player, out -> {
|
||||
out.writeUTF("TP_POS");
|
||||
out.writeUTF(player.getName());
|
||||
out.writeUTF(server);
|
||||
out.writeUTF(TS_CHANNEL);
|
||||
// Sub-payload
|
||||
ByteArrayOutputStream sub = new ByteArrayOutputStream();
|
||||
DataOutputStream subOut = new DataOutputStream(sub);
|
||||
subOut.writeUTF("LOCATION");
|
||||
subOut.writeUTF(targetPlayer);
|
||||
subOut.writeUTF(world);
|
||||
subOut.writeDouble(x);
|
||||
subOut.writeDouble(y);
|
||||
subOut.writeDouble(z);
|
||||
subOut.writeFloat(yaw);
|
||||
subOut.writeFloat(pitch);
|
||||
byte[] subBytes = sub.toByteArray();
|
||||
out.writeShort(subBytes.length);
|
||||
out.write(subBytes);
|
||||
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("TS Forward Fehler: " + e.getMessage());
|
||||
}
|
||||
out.writeUTF(world);
|
||||
out.writeDouble(x);
|
||||
out.writeDouble(y);
|
||||
out.writeDouble(z);
|
||||
out.writeFloat(yaw);
|
||||
out.writeFloat(pitch);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendPlayerToPlayerPayload(Player sender, String moverPlayer, String targetPlayer, String targetServer) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("Forward");
|
||||
out.writeUTF(targetServer);
|
||||
out.writeUTF(TS_CHANNEL);
|
||||
// TPA
|
||||
|
||||
ByteArrayOutputStream sub = new ByteArrayOutputStream();
|
||||
DataOutputStream subOut = new DataOutputStream(sub);
|
||||
subOut.writeUTF("TP_TO_PLAYER");
|
||||
subOut.writeUTF(moverPlayer);
|
||||
subOut.writeUTF(targetPlayer);
|
||||
|
||||
byte[] subBytes = sub.toByteArray();
|
||||
out.writeShort(subBytes.length);
|
||||
out.write(subBytes);
|
||||
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("TS Player->Player Forward Fehler: " + e.getMessage());
|
||||
}
|
||||
public void sendTpaRequest(Player from, String toName,
|
||||
String type, String msgToTarget, String msgToRequester) {
|
||||
send(from, out -> {
|
||||
out.writeUTF("TPA_REQUEST");
|
||||
out.writeUTF(from.getName());
|
||||
out.writeUTF(toName);
|
||||
out.writeUTF(type);
|
||||
out.writeUTF(msgToTarget);
|
||||
out.writeUTF(msgToRequester);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendForwardToPlayer(Player sender, String targetPlayer, IOConsumer<DataOutputStream> payloadWriter) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
out.writeUTF("ForwardToPlayer");
|
||||
out.writeUTF(targetPlayer);
|
||||
out.writeUTF(TS_CHANNEL);
|
||||
|
||||
ByteArrayOutputStream sub = new ByteArrayOutputStream();
|
||||
DataOutputStream subOut = new DataOutputStream(sub);
|
||||
payloadWriter.accept(subOut);
|
||||
|
||||
byte[] subBytes = sub.toByteArray();
|
||||
out.writeShort(subBytes.length);
|
||||
out.write(subBytes);
|
||||
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("TS ForwardToPlayer Fehler: " + e.getMessage());
|
||||
}
|
||||
public void sendTpaAccept(Player acceptor, String fromName, String errMsg) {
|
||||
send(acceptor, out -> {
|
||||
out.writeUTF("TPA_ACCEPT");
|
||||
out.writeUTF(acceptor.getName());
|
||||
out.writeUTF(fromName == null ? "nu" : fromName);
|
||||
out.writeUTF(errMsg);
|
||||
});
|
||||
}
|
||||
|
||||
private void schedulePlayerToPlayerTeleport(String moverName, String targetName) {
|
||||
int maxAttempts = 100;
|
||||
final int[] attempts = {0};
|
||||
final int[] taskId = {0};
|
||||
|
||||
taskId[0] = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
|
||||
attempts[0]++;
|
||||
Player mover = plugin.getServer().getPlayerExact(moverName);
|
||||
Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
|
||||
if (mover != null && mover.isOnline() && target != null && target.isOnline()) {
|
||||
plugin.getTeleportManager().teleport(
|
||||
mover,
|
||||
new de.teleportsuite.models.TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()),
|
||||
false
|
||||
);
|
||||
plugin.getServer().getScheduler().cancelTask(taskId[0]);
|
||||
return;
|
||||
public void sendTpaDeny(Player denier, String fromName, String msg) {
|
||||
send(denier, out -> {
|
||||
out.writeUTF("TPA_DENY");
|
||||
out.writeUTF(denier.getName());
|
||||
out.writeUTF(fromName == null ? "nu" : fromName);
|
||||
out.writeUTF(msg);
|
||||
});
|
||||
}
|
||||
|
||||
if (attempts[0] >= maxAttempts) {
|
||||
plugin.getServer().getScheduler().cancelTask(taskId[0]);
|
||||
}
|
||||
}, 10L, 10L);
|
||||
public void sendTpaCancel(Player canceller, String msgFrom, String msgTo) {
|
||||
send(canceller, out -> {
|
||||
out.writeUTF("TPA_CANCEL");
|
||||
out.writeUTF(canceller.getName());
|
||||
out.writeUTF(msgFrom);
|
||||
out.writeUTF(msgTo);
|
||||
});
|
||||
}
|
||||
|
||||
private void flushServerLookupCallbacks(String key, String server) {
|
||||
List<Consumer<String>> callbacks = pendingServerLookups.remove(key);
|
||||
if (callbacks == null) return;
|
||||
for (Consumer<String> callback : callbacks) {
|
||||
callback.accept(server);
|
||||
}
|
||||
public void sendTpaExpired(Player from, String toName, String msgFrom, String msgTo) {
|
||||
send(from, out -> {
|
||||
out.writeUTF("TPA_EXPIRED");
|
||||
out.writeUTF(from.getName());
|
||||
out.writeUTF(toName);
|
||||
out.writeUTF(msgFrom);
|
||||
out.writeUTF(msgTo);
|
||||
});
|
||||
}
|
||||
|
||||
private void flushPlayerListCallbacks(String key, List<String> players) {
|
||||
List<Consumer<List<String>>> callbacks = pendingPlayerListLookups.remove(key);
|
||||
if (callbacks == null) return;
|
||||
for (Consumer<List<String>> callback : callbacks) {
|
||||
callback.accept(players);
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// Incoming handler (Bungee → Spigot)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||
if (channel.equals(BUNGEE_CHANNEL)) {
|
||||
handleBungeeMessage(message);
|
||||
return;
|
||||
public void onPluginMessageReceived(String channel, Player unused, byte[] bytes) {
|
||||
if (!channel.equals(CHANNEL_TO_SPIGOT)) return;
|
||||
|
||||
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes))) {
|
||||
String task = in.readUTF();
|
||||
|
||||
switch (task) {
|
||||
|
||||
// Bungee hat sender bereits auf diesen Server gewechselt.
|
||||
// Sobald beide Spieler lokal online sind, teleportieren.
|
||||
case "TP_PLAYERTOPLAYER": {
|
||||
String senderName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
schedulePlayerToPlayerTeleportWithBack(senderName, targetName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (channel.equals(TS_CHANNEL)) {
|
||||
handleTeleportSuiteMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBungeeMessage(byte[] message) {
|
||||
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
|
||||
String subChannel = in.readUTF();
|
||||
if ("GetPlayerServer".equals(subChannel)) {
|
||||
// Bungee hat player bereits auf diesen Server gewechselt.
|
||||
// Nach kurzem Delay an exakte Koordinaten teleportieren.
|
||||
case "TP_PLAYERTOPOSITION": {
|
||||
String playerName = in.readUTF();
|
||||
String serverName = in.readUTF();
|
||||
if ("null".equalsIgnoreCase(serverName) || serverName.isBlank()) {
|
||||
serverName = null;
|
||||
}
|
||||
flushServerLookupCallbacks(playerName.toLowerCase(Locale.ROOT), serverName);
|
||||
return;
|
||||
String world = in.readUTF();
|
||||
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
|
||||
float yaw = in.readFloat(), pitch = in.readFloat();
|
||||
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
org.bukkit.entity.Player p = plugin.getServer().getPlayerExact(playerName);
|
||||
if (p == null || !p.isOnline()) return;
|
||||
// Save back location BEFORE teleporting (teleport() with saveBack=false
|
||||
// skips this, so we do it manually here for cross-server teleports)
|
||||
plugin.getDatabaseManager().saveLastLocation(p.getUniqueId(),
|
||||
new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
TeleportLocation dest = new TeleportLocation(
|
||||
world, x, y, z, yaw, pitch,
|
||||
plugin.getConfigManager().getServerName());
|
||||
plugin.getTeleportManager().teleport(p, dest, false);
|
||||
}, 10L);
|
||||
break;
|
||||
}
|
||||
|
||||
if ("PlayerList".equals(subChannel)) {
|
||||
String scope = in.readUTF();
|
||||
String playerCsv = in.readUTF();
|
||||
List<String> players = new ArrayList<>();
|
||||
if (playerCsv != null && !playerCsv.isBlank()) {
|
||||
for (String name : playerCsv.split(",")) {
|
||||
String trimmed = name.trim();
|
||||
if (!trimmed.isEmpty()) players.add(trimmed);
|
||||
// Direkte Chat-Nachricht an einen lokalen Spieler
|
||||
case "SEND_MSG": {
|
||||
String playerName = in.readUTF();
|
||||
String message = in.readUTF();
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
org.bukkit.entity.Player p = plugin.getServer().getPlayerExact(playerName);
|
||||
if (p != null && p.isOnline()) p.sendMessage(message);
|
||||
}, 1L);
|
||||
break;
|
||||
}
|
||||
}
|
||||
flushPlayerListCallbacks(scope.toUpperCase(Locale.ROOT), players);
|
||||
return;
|
||||
|
||||
default:
|
||||
plugin.getLogger().warning("Unbekannter Bungee-Task: " + task);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Fehler beim Lesen der Bungee-Nachricht: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTeleportSuiteMessage(byte[] message) {
|
||||
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
|
||||
String payloadType = in.readUTF();
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
if ("LOCATION".equals(payloadType)) {
|
||||
String targetName = in.readUTF();
|
||||
String world = in.readUTF();
|
||||
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
|
||||
float yaw = in.readFloat(), pitch = in.readFloat();
|
||||
/**
|
||||
* Wartet bis sender UND target lokal online sind (max. 100 × 10 Ticks = 50 s),
|
||||
* speichert Back-Location und führt dann player.teleport() aus.
|
||||
*/
|
||||
private void schedulePlayerToPlayerTeleportWithBack(String senderName, String targetName) {
|
||||
final int[] attempts = {0};
|
||||
final int[] taskId = {-1};
|
||||
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
if (target == null || !target.isOnline()) return;
|
||||
de.teleportsuite.models.TeleportLocation loc =
|
||||
new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
|
||||
plugin.getConfigManager().getServerName());
|
||||
plugin.getTeleportManager().teleport(target, loc, false);
|
||||
}, 20L);
|
||||
taskId[0] = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
|
||||
attempts[0]++;
|
||||
org.bukkit.entity.Player sender = plugin.getServer().getPlayerExact(senderName);
|
||||
org.bukkit.entity.Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
|
||||
if (sender != null && sender.isOnline() && target != null && target.isOnline()) {
|
||||
// Save back location before teleport so /back works cross-server
|
||||
plugin.getDatabaseManager().saveLastLocation(sender.getUniqueId(),
|
||||
new TeleportLocation(sender.getLocation(),
|
||||
plugin.getConfigManager().getServerName()));
|
||||
plugin.getTeleportManager().teleport(
|
||||
sender,
|
||||
new TeleportLocation(target.getLocation(),
|
||||
plugin.getConfigManager().getServerName()),
|
||||
false);
|
||||
plugin.getServer().getScheduler().cancelTask(taskId[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("TP_TO_PLAYER".equals(payloadType)) {
|
||||
String moverName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
schedulePlayerToPlayerTeleport(moverName, targetName);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("TPA_REQUEST".equals(payloadType)) {
|
||||
String requesterName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
plugin.getTeleportManager().receiveCrossServerTpaRequest(requesterName, targetName);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("TPA_ACCEPT".equals(payloadType)) {
|
||||
String requesterName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
plugin.getTeleportManager().receiveCrossServerTpaAccepted(requesterName, targetName);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("TPA_DENY".equals(payloadType)) {
|
||||
String requesterName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
plugin.getTeleportManager().receiveCrossServerTpaDenied(requesterName, targetName);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("TPA_EXPIRED".equals(payloadType)) {
|
||||
String requesterName = in.readUTF();
|
||||
String targetName = in.readUTF();
|
||||
plugin.getTeleportManager().receiveCrossServerTpaExpired(requesterName, targetName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Backward compatibility with old payload format
|
||||
String targetName = payloadType;
|
||||
String world = in.readUTF();
|
||||
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
|
||||
float yaw = in.readFloat(), pitch = in.readFloat();
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
if (target == null || !target.isOnline()) return;
|
||||
de.teleportsuite.models.TeleportLocation loc =
|
||||
new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
|
||||
plugin.getConfigManager().getServerName());
|
||||
plugin.getTeleportManager().teleport(target, loc, false);
|
||||
}, 20L);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Fehler beim Lesen der TS-Nachricht: " + e.getMessage());
|
||||
if (attempts[0] >= 100) {
|
||||
plugin.getServer().getScheduler().cancelTask(taskId[0]);
|
||||
}
|
||||
}, 5L, 10L);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface IOConsumer<T> {
|
||||
void accept(T value) throws IOException;
|
||||
private interface IOWriter {
|
||||
void write(DataOutputStream out) throws IOException;
|
||||
}
|
||||
|
||||
/** Schreibt ein Payload und schickt es über den Bungee-Kanal. */
|
||||
private void send(Player anchor, IOWriter writer) {
|
||||
if (anchor == null || !anchor.isOnline()) return;
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos)) {
|
||||
writer.write(out);
|
||||
anchor.sendPluginMessage(plugin, CHANNEL_TO_BUNGEE, bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Bungee-Send Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
package de.teleportsuite.commands;
|
||||
|
||||
import de.teleportsuite.TeleportSuite;
|
||||
import de.teleportsuite.models.TeleportLocation;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class TpAllCommand implements CommandExecutor {
|
||||
private final TeleportSuite plugin;
|
||||
public TpAllCommand(TeleportSuite p) { this.plugin = p; }
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||
if (!(sender instanceof Player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
|
||||
Player p = (Player) sender;
|
||||
if (!p.hasPermission("teleportsuite.tpall")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
|
||||
|
||||
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportAllPlayersToLocalPlayer(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.getBungeeMessenger().teleportAll(p);
|
||||
} else {
|
||||
TeleportLocation dest = new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName());
|
||||
for (Player online : Bukkit.getOnlinePlayers()) {
|
||||
for (Player online : plugin.getServer().getOnlinePlayers()) {
|
||||
if (!online.equals(p)) plugin.getTeleportManager().teleport(online, dest);
|
||||
}
|
||||
p.sendMessage("§aAlle Spieler wurden zu dir teleportiert.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,28 +19,24 @@ public class TpCommand implements CommandExecutor {
|
||||
|
||||
if (args.length == 1) {
|
||||
Player target = Bukkit.getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportToPlayer(p, args[0]);
|
||||
return true;
|
||||
}
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
|
||||
return true;
|
||||
}
|
||||
if (target != null) {
|
||||
plugin.getTeleportManager().teleport(p, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
} else if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportToPlayer(p, args[0]);
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", args[0]));
|
||||
}
|
||||
} else {
|
||||
if (!p.hasPermission("teleportsuite.admin")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
|
||||
Player from = Bukkit.getPlayer(args[0]);
|
||||
Player to = Bukkit.getPlayer(args[1]);
|
||||
if (from == null || to == null) {
|
||||
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportAnyPlayerToAnyPlayer(p, args[0], args[1]);
|
||||
return true;
|
||||
}
|
||||
p.sendMessage("§cEin Spieler nicht gefunden.");
|
||||
return true;
|
||||
}
|
||||
if (from != null && to != null) {
|
||||
plugin.getTeleportManager().teleport(from, new TeleportLocation(to.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
} else if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportPlayerToPlayer(p, args[0], args[1]);
|
||||
} else {
|
||||
p.sendMessage("§cEin Spieler nicht gefunden.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,15 +17,13 @@ public class TpHereCommand implements CommandExecutor {
|
||||
if (!p.hasPermission("teleportsuite.tphere")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
|
||||
if (args.length < 1) { p.sendMessage("§cVerwendung: /tphere <spieler>"); return true; }
|
||||
Player target = Bukkit.getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportPlayerToLocalPlayer(p, args[0], p.getName());
|
||||
return true;
|
||||
}
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
|
||||
return true;
|
||||
}
|
||||
if (target != null) {
|
||||
plugin.getTeleportManager().teleport(target, new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
} else if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportHere(p, args[0]);
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", args[0]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,17 @@ public class TpaCommand implements CommandExecutor {
|
||||
if (!p.hasPermission("teleportsuite.tpa")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
|
||||
if (args.length < 1) { p.sendMessage("§cVerwendung: /tpa <spieler>"); return true; }
|
||||
if (p.getName().equalsIgnoreCase(args[0])) { p.sendMessage("§cDu kannst dir nicht selbst eine Anfrage senden."); return true; }
|
||||
|
||||
Player target = Bukkit.getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
plugin.getTeleportManager().sendTpaRequest(p, args[0]);
|
||||
return true;
|
||||
}
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
|
||||
return true;
|
||||
}
|
||||
if (target != null) {
|
||||
// Same server
|
||||
plugin.getTeleportManager().sendTpaRequest(p, target);
|
||||
} else if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
|
||||
// Cross-server — Bungee manages the pending state
|
||||
plugin.getTeleportManager().sendCrossServerTpaRequest(p, args[0]);
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", args[0]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,20 +131,25 @@ public class DatabaseManager {
|
||||
|
||||
// ===== WARPS =====
|
||||
public void saveWarp(String name, TeleportLocation loc, UUID creator, String permission) {
|
||||
String sql = isMySQL
|
||||
? "INSERT INTO ts_warps (name,world,x,y,z,yaw,pitch,server,creator,permission) VALUES(?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE world=?,x=?,y=?,z=?,yaw=?,pitch=?,server=?"
|
||||
: "INSERT OR REPLACE INTO ts_warps (name,world,x,y,z,yaw,pitch,server,creator,permission) VALUES(?,?,?,?,?,?,?,?,?,?)";
|
||||
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, name); ps.setString(2, loc.getWorld());
|
||||
ps.setDouble(3, loc.getX()); ps.setDouble(4, loc.getY()); ps.setDouble(5, loc.getZ());
|
||||
ps.setFloat(6, loc.getYaw()); ps.setFloat(7, loc.getPitch()); ps.setString(8, loc.getServer());
|
||||
ps.setString(9, creator != null ? creator.toString() : null); ps.setString(10, permission);
|
||||
if (isMySQL) {
|
||||
ps.setString(11, loc.getWorld()); ps.setDouble(12, loc.getX()); ps.setDouble(13, loc.getY());
|
||||
ps.setDouble(14, loc.getZ()); ps.setFloat(15, loc.getYaw()); ps.setFloat(16, loc.getPitch());
|
||||
ps.setString(17, loc.getServer());
|
||||
try (Connection c = getConnection()) {
|
||||
boolean exists;
|
||||
try (PreparedStatement check = c.prepareStatement("SELECT 1 FROM ts_warps WHERE name=?")) {
|
||||
check.setString(1, name);
|
||||
exists = check.executeQuery().next();
|
||||
}
|
||||
String sql = exists
|
||||
? "UPDATE ts_warps SET world=?,x=?,y=?,z=?,yaw=?,pitch=?,server=?,creator=?,permission=? WHERE name=?"
|
||||
: "INSERT INTO ts_warps (world,x,y,z,yaw,pitch,server,creator,permission,name) VALUES(?,?,?,?,?,?,?,?,?,?)";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, loc.getWorld());
|
||||
ps.setDouble(2, loc.getX()); ps.setDouble(3, loc.getY()); ps.setDouble(4, loc.getZ());
|
||||
ps.setFloat(5, loc.getYaw()); ps.setFloat(6, loc.getPitch());
|
||||
ps.setString(7, loc.getServer());
|
||||
ps.setString(8, creator != null ? creator.toString() : null);
|
||||
ps.setString(9, permission);
|
||||
ps.setString(10, name);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) { plugin.getLogger().warning("saveWarp: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
@@ -250,19 +255,23 @@ public class DatabaseManager {
|
||||
|
||||
// ===== PLAYER DATA =====
|
||||
public void saveLastLocation(UUID uuid, TeleportLocation loc) {
|
||||
String sql = isMySQL
|
||||
? "INSERT INTO ts_player_data (uuid,last_world,last_x,last_y,last_z,last_yaw,last_pitch,last_server) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE last_world=?,last_x=?,last_y=?,last_z=?,last_yaw=?,last_pitch=?,last_server=?"
|
||||
: "INSERT OR REPLACE INTO ts_player_data (uuid,last_world,last_x,last_y,last_z,last_yaw,last_pitch,last_server) VALUES(?,?,?,?,?,?,?,?)";
|
||||
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, uuid.toString()); ps.setString(2, loc.getWorld());
|
||||
ps.setDouble(3, loc.getX()); ps.setDouble(4, loc.getY()); ps.setDouble(5, loc.getZ());
|
||||
ps.setFloat(6, loc.getYaw()); ps.setFloat(7, loc.getPitch()); ps.setString(8, loc.getServer());
|
||||
if (isMySQL) {
|
||||
ps.setString(9, loc.getWorld()); ps.setDouble(10, loc.getX()); ps.setDouble(11, loc.getY());
|
||||
ps.setDouble(12, loc.getZ()); ps.setFloat(13, loc.getYaw()); ps.setFloat(14, loc.getPitch());
|
||||
ps.setString(15, loc.getServer());
|
||||
try (Connection c = getConnection()) {
|
||||
// Check if row exists
|
||||
boolean exists;
|
||||
try (PreparedStatement check = c.prepareStatement("SELECT 1 FROM ts_player_data WHERE uuid=?")) {
|
||||
check.setString(1, uuid.toString());
|
||||
exists = check.executeQuery().next();
|
||||
}
|
||||
String sql = exists
|
||||
? "UPDATE ts_player_data SET last_world=?,last_x=?,last_y=?,last_z=?,last_yaw=?,last_pitch=?,last_server=? WHERE uuid=?"
|
||||
: "INSERT INTO ts_player_data (last_world,last_x,last_y,last_z,last_yaw,last_pitch,last_server,uuid) VALUES(?,?,?,?,?,?,?,?)";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, loc.getWorld());
|
||||
ps.setDouble(2, loc.getX()); ps.setDouble(3, loc.getY()); ps.setDouble(4, loc.getZ());
|
||||
ps.setFloat(5, loc.getYaw()); ps.setFloat(6, loc.getPitch());
|
||||
ps.setString(7, loc.getServer()); ps.setString(8, uuid.toString());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) { plugin.getLogger().warning("saveLastLocation: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ package de.teleportsuite.listeners;
|
||||
|
||||
import de.teleportsuite.TeleportSuite;
|
||||
import de.teleportsuite.models.TeleportLocation;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
|
||||
@@ -10,12 +12,33 @@ public class PlayerRespawnListener implements Listener {
|
||||
private final TeleportSuite plugin;
|
||||
public PlayerRespawnListener(TeleportSuite plugin) { this.plugin = plugin; }
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onRespawn(PlayerRespawnEvent event) {
|
||||
if (!plugin.getConfigManager().deathRespawnToSpawn()) return;
|
||||
|
||||
TeleportLocation spawn = plugin.getDatabaseManager().getSpawn("spawn");
|
||||
if (spawn == null) return;
|
||||
var loc = spawn.toBukkitLocation();
|
||||
if (loc != null) event.setRespawnLocation(loc);
|
||||
if (spawn == null) {
|
||||
plugin.getLogger().warning("death-respawn-to-spawn ist aktiv, aber kein Spawn gesetzt!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn ist auf einem anderen Server → nach dem Respawn teleportieren
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
if (!spawn.isLocalServer(localServer)) {
|
||||
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
if (event.getPlayer().isOnline()) {
|
||||
plugin.getTeleportManager().teleport(event.getPlayer(), spawn, false);
|
||||
}
|
||||
}, 5L);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn ist lokal → direkt als Respawn-Location setzen
|
||||
Location loc = spawn.toBukkitLocation();
|
||||
if (loc == null || loc.getWorld() == null) {
|
||||
plugin.getLogger().warning("Spawn-Welt \"" + spawn.getWorld() + "\" nicht gefunden!");
|
||||
return;
|
||||
}
|
||||
event.setRespawnLocation(loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.teleportsuite.managers;
|
||||
|
||||
import de.teleportsuite.TeleportSuite;
|
||||
import de.teleportsuite.bungeemessaging.BungeeMessenger;
|
||||
import de.teleportsuite.models.TeleportLocation;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -10,21 +11,22 @@ import java.util.*;
|
||||
|
||||
public class TeleportManager {
|
||||
private final TeleportSuite plugin;
|
||||
// Pending TPA requests: requester -> target
|
||||
// Same-server pending TPA: requesterUUID → targetUUID
|
||||
private final Map<UUID, UUID> tpaRequests = new HashMap<>();
|
||||
private final Map<UUID, Long> requestTimestamps = new HashMap<>();
|
||||
// Pending cross-server TPA requests: requesterName -> targetName
|
||||
private final Map<String, String> crossServerTpaRequests = new HashMap<>();
|
||||
private final Map<String, Long> crossServerRequestTimestamps = new HashMap<>();
|
||||
// Cooldowns
|
||||
private final Map<UUID, Long> cooldowns = new HashMap<>();
|
||||
// Warmup tasks
|
||||
private final Map<UUID, Integer> warmupTasks = new HashMap<>();
|
||||
// Saved "before teleport" locations (for /back)
|
||||
// Back-locations (saved before every teleport)
|
||||
private final Map<UUID, TeleportLocation> pendingBackLocations = new HashMap<>();
|
||||
|
||||
public TeleportManager(TeleportSuite plugin) { this.plugin = plugin; }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main teleport entry point
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public void teleport(Player player, TeleportLocation destination) {
|
||||
teleport(player, destination, true);
|
||||
}
|
||||
@@ -33,31 +35,36 @@ public class TeleportManager {
|
||||
int delay = plugin.getConfigManager().getTeleportDelay();
|
||||
if (player.hasPermission("teleportsuite.nodelay")) delay = 0;
|
||||
|
||||
// Check cooldown
|
||||
if (!player.hasPermission("teleportsuite.nocooldown")) {
|
||||
long cooldownMs = plugin.getConfigManager().getTeleportCooldown() * 1000L;
|
||||
long lastTp = cooldowns.getOrDefault(player.getUniqueId(), 0L);
|
||||
if (System.currentTimeMillis() - lastTp < cooldownMs) {
|
||||
long remaining = (cooldownMs - (System.currentTimeMillis() - lastTp)) / 1000;
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("teleport-cooldown", "seconds", String.valueOf(remaining)));
|
||||
player.sendMessage(plugin.getConfigManager().getMessage(
|
||||
"teleport-cooldown", "seconds", String.valueOf(remaining)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (saveBack) pendingBackLocations.put(player.getUniqueId(), new TeleportLocation(player.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
if (saveBack) {
|
||||
pendingBackLocations.put(player.getUniqueId(),
|
||||
new TeleportLocation(player.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
}
|
||||
|
||||
if (delay <= 0) {
|
||||
executeTeleport(player, destination);
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("teleport-warmup", "seconds", String.valueOf(delay)));
|
||||
player.sendMessage(plugin.getConfigManager().getMessage(
|
||||
"teleport-warmup", "seconds", String.valueOf(delay)));
|
||||
Location startLoc = player.getLocation().clone();
|
||||
|
||||
int taskId = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
if (plugin.getConfigManager().cancelOnMove()) {
|
||||
Location now = player.getLocation();
|
||||
if (Math.abs(now.getX()-startLoc.getX()) > 0.5 || Math.abs(now.getZ()-startLoc.getZ()) > 0.5) {
|
||||
if (Math.abs(now.getX() - startLoc.getX()) > 0.5
|
||||
|| Math.abs(now.getZ() - startLoc.getZ()) > 0.5) {
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("teleport-cancelled"));
|
||||
warmupTasks.remove(player.getUniqueId());
|
||||
return;
|
||||
@@ -73,10 +80,17 @@ public class TeleportManager {
|
||||
private void executeTeleport(Player player, TeleportLocation dest) {
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
|
||||
// Save last location + apply cooldown always (also for cross-server)
|
||||
plugin.getDatabaseManager().saveLastLocation(player.getUniqueId(),
|
||||
new TeleportLocation(player.getLocation(), localServer));
|
||||
cooldowns.put(player.getUniqueId(), System.currentTimeMillis());
|
||||
|
||||
if (!dest.isLocalServer(localServer)) {
|
||||
// BungeeCord-Teleport
|
||||
if (plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().connectToServer(player, dest.getServer(), dest.getWorld(),
|
||||
// Cross-server: delegate completely to the Bungee plugin.
|
||||
// It will call sender.connect() and then send TP_PLAYERTOPOSITION.
|
||||
BungeeMessenger bm = plugin.getBungeeMessenger();
|
||||
if (bm != null) {
|
||||
bm.teleportToPosition(player, dest.getServer(), dest.getWorld(),
|
||||
dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch());
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||
}
|
||||
@@ -85,16 +99,14 @@ public class TeleportManager {
|
||||
|
||||
Location loc = dest.toBukkitLocation();
|
||||
if (loc == null || loc.getWorld() == null) {
|
||||
player.sendMessage("§cZielwelt nicht gefunden!");
|
||||
player.sendMessage("§cFehler: Zielwelt §6" + dest.getWorld() + "§c nicht gefunden!"
|
||||
+ " Ist der Spawn auf diesem Server gesetzt?");
|
||||
plugin.getLogger().warning("Teleport fehlgeschlagen: Welt '" + dest.getWorld()
|
||||
+ "' fuer Spieler " + player.getName() + " nicht geladen.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save last location to database
|
||||
plugin.getDatabaseManager().saveLastLocation(player.getUniqueId(),
|
||||
new TeleportLocation(player.getLocation(), localServer));
|
||||
|
||||
player.teleport(loc);
|
||||
cooldowns.put(player.getUniqueId(), System.currentTimeMillis());
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||
}
|
||||
|
||||
@@ -107,7 +119,10 @@ public class TeleportManager {
|
||||
return pendingBackLocations.get(uuid);
|
||||
}
|
||||
|
||||
// TPA
|
||||
// -------------------------------------------------------------------------
|
||||
// Same-server TPA
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public void sendTpaRequest(Player from, Player to) {
|
||||
tpaRequests.put(from.getUniqueId(), to.getUniqueId());
|
||||
requestTimestamps.put(from.getUniqueId(), System.currentTimeMillis());
|
||||
@@ -120,186 +135,89 @@ public class TeleportManager {
|
||||
if (tpaRequests.containsKey(from.getUniqueId())) {
|
||||
tpaRequests.remove(from.getUniqueId());
|
||||
requestTimestamps.remove(from.getUniqueId());
|
||||
if (from.isOnline()) from.sendMessage(plugin.getConfigManager().getMessage("tpa-expired"));
|
||||
if (from.isOnline()) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("tpa-expired"));
|
||||
}
|
||||
}
|
||||
}, timeout * 20L);
|
||||
}
|
||||
|
||||
public void sendTpaRequest(Player from, String targetName) {
|
||||
if (plugin.getBungeeMessenger() == null) {
|
||||
// -------------------------------------------------------------------------
|
||||
// Cross-server TPA — all state lives on the Bungee plugin now;
|
||||
// Spigot just sends the right payload and handles local accept/deny.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called by TpaCommand when the target player is NOT on this server.
|
||||
* Bungee will manage the pending state and route all messages.
|
||||
*/
|
||||
public void sendCrossServerTpaRequest(Player from, String targetName) {
|
||||
BungeeMessenger bm = plugin.getBungeeMessenger();
|
||||
if (bm == null) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getBungeeMessenger().requestPlayerServer(from, targetName, server -> {
|
||||
if (server == null || server.isBlank()) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
|
||||
return;
|
||||
}
|
||||
|
||||
String localServer = plugin.getConfigManager().getServerName();
|
||||
if (server.equalsIgnoreCase(localServer)) {
|
||||
Player localTarget = Bukkit.getPlayerExact(targetName);
|
||||
if (localTarget == null || !localTarget.isOnline()) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
|
||||
return;
|
||||
}
|
||||
sendTpaRequest(from, localTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
String requesterKey = from.getName();
|
||||
crossServerTpaRequests.put(requesterKey, targetName);
|
||||
crossServerRequestTimestamps.put(requesterKey, System.currentTimeMillis());
|
||||
|
||||
plugin.getBungeeMessenger().sendTpaRequestToPlayer(from, targetName);
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("tpa-sent", "player", targetName));
|
||||
|
||||
int timeout = plugin.getConfigManager().getRequestTimeout();
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
if (!crossServerTpaRequests.containsKey(requesterKey)) return;
|
||||
crossServerTpaRequests.remove(requesterKey);
|
||||
crossServerRequestTimestamps.remove(requesterKey);
|
||||
|
||||
Player requester = Bukkit.getPlayerExact(from.getName());
|
||||
if (requester != null && requester.isOnline()) {
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-expired"));
|
||||
}
|
||||
if (plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().sendTpaExpiredToPlayer(from, targetName);
|
||||
}
|
||||
}, timeout * 20L);
|
||||
});
|
||||
}
|
||||
|
||||
public void receiveCrossServerTpaRequest(String requesterName, String targetName) {
|
||||
Player target = Bukkit.getPlayerExact(targetName);
|
||||
if (target == null || !target.isOnline()) return;
|
||||
|
||||
crossServerTpaRequests.put(requesterName, targetName);
|
||||
crossServerRequestTimestamps.put(requesterName, System.currentTimeMillis());
|
||||
target.sendMessage(plugin.getConfigManager().getMessage("tpa-received", "player", requesterName));
|
||||
}
|
||||
|
||||
public void receiveCrossServerTpaAccepted(String requesterName, String targetName) {
|
||||
Player requester = Bukkit.getPlayerExact(requesterName);
|
||||
if (requester == null || !requester.isOnline()) return;
|
||||
|
||||
String requesterKey = findRequesterKey(requesterName);
|
||||
if (requesterKey != null) {
|
||||
crossServerTpaRequests.remove(requesterKey);
|
||||
crossServerRequestTimestamps.remove(requesterKey);
|
||||
}
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-accepted", "player", targetName));
|
||||
|
||||
if (plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().teleportToPlayer(requester, targetName);
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveCrossServerTpaDenied(String requesterName, String targetName) {
|
||||
Player requester = Bukkit.getPlayerExact(requesterName);
|
||||
if (requester == null || !requester.isOnline()) return;
|
||||
|
||||
String requesterKey = findRequesterKey(requesterName);
|
||||
if (requesterKey != null) {
|
||||
crossServerTpaRequests.remove(requesterKey);
|
||||
crossServerRequestTimestamps.remove(requesterKey);
|
||||
}
|
||||
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-denied", "player", targetName));
|
||||
}
|
||||
|
||||
public void receiveCrossServerTpaExpired(String requesterName, String targetName) {
|
||||
Player target = Bukkit.getPlayerExact(targetName);
|
||||
if (target == null || !target.isOnline()) return;
|
||||
|
||||
String requesterKey = findRequesterKey(requesterName);
|
||||
if (requesterKey != null && target.getName().equalsIgnoreCase(crossServerTpaRequests.get(requesterKey))) {
|
||||
crossServerTpaRequests.remove(requesterKey);
|
||||
crossServerRequestTimestamps.remove(requesterKey);
|
||||
}
|
||||
bm.sendTpaRequest(
|
||||
from, targetName, "TPTO",
|
||||
plugin.getConfigManager().getMessage("tpa-received", "player", from.getName()),
|
||||
plugin.getConfigManager().getMessage("tpa-sent", "player", targetName));
|
||||
}
|
||||
|
||||
/** Called by TpAcceptCommand — handles both same-server and cross-server. */
|
||||
public boolean acceptTpa(Player target) {
|
||||
UUID requester = null;
|
||||
// Same-server accept
|
||||
UUID requesterUUID = null;
|
||||
for (Map.Entry<UUID, UUID> entry : tpaRequests.entrySet()) {
|
||||
if (entry.getValue().equals(target.getUniqueId())) {
|
||||
requester = entry.getKey();
|
||||
requesterUUID = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (requester != null) {
|
||||
Player from = Bukkit.getPlayer(requester);
|
||||
tpaRequests.remove(requester);
|
||||
requestTimestamps.remove(requester);
|
||||
|
||||
if (requesterUUID != null) {
|
||||
Player from = Bukkit.getPlayer(requesterUUID);
|
||||
tpaRequests.remove(requesterUUID);
|
||||
requestTimestamps.remove(requesterUUID);
|
||||
if (from != null && from.isOnline()) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("tpa-accepted", "player", target.getName()));
|
||||
teleport(from, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()));
|
||||
teleport(from, new TeleportLocation(target.getLocation(),
|
||||
plugin.getConfigManager().getServerName()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String requesterName = findRequesterByTarget(target.getName());
|
||||
|
||||
if (requesterName == null) return false;
|
||||
|
||||
crossServerTpaRequests.remove(requesterName);
|
||||
crossServerRequestTimestamps.remove(requesterName);
|
||||
|
||||
if (plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().sendTpaAcceptToPlayer(target, requesterName);
|
||||
}
|
||||
// Cross-server accept — tell Bungee, it has the pending state
|
||||
BungeeMessenger bm = plugin.getBungeeMessenger();
|
||||
if (bm == null) return false;
|
||||
bm.sendTpaAccept(target, null,
|
||||
plugin.getConfigManager().getMessage("tpa-request-not-found"));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Called by TpDenyCommand — handles both same-server and cross-server. */
|
||||
public boolean denyTpa(Player target) {
|
||||
UUID requester = null;
|
||||
// Same-server deny
|
||||
UUID requesterUUID = null;
|
||||
for (Map.Entry<UUID, UUID> entry : tpaRequests.entrySet()) {
|
||||
if (entry.getValue().equals(target.getUniqueId())) {
|
||||
requester = entry.getKey();
|
||||
requesterUUID = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (requester != null) {
|
||||
Player from = Bukkit.getPlayer(requester);
|
||||
tpaRequests.remove(requester);
|
||||
requestTimestamps.remove(requester);
|
||||
|
||||
if (requesterUUID != null) {
|
||||
Player from = Bukkit.getPlayer(requesterUUID);
|
||||
tpaRequests.remove(requesterUUID);
|
||||
requestTimestamps.remove(requesterUUID);
|
||||
if (from != null && from.isOnline()) {
|
||||
from.sendMessage(plugin.getConfigManager().getMessage("tpa-denied", "player", target.getName()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String requesterName = findRequesterByTarget(target.getName());
|
||||
|
||||
if (requesterName == null) return false;
|
||||
|
||||
crossServerTpaRequests.remove(requesterName);
|
||||
crossServerRequestTimestamps.remove(requesterName);
|
||||
|
||||
if (plugin.getBungeeMessenger() != null) {
|
||||
plugin.getBungeeMessenger().sendTpaDenyToPlayer(target, requesterName);
|
||||
}
|
||||
// Cross-server deny
|
||||
BungeeMessenger bm = plugin.getBungeeMessenger();
|
||||
if (bm == null) return false;
|
||||
bm.sendTpaDeny(target, null,
|
||||
plugin.getConfigManager().getMessage("tpa-denied", "player", target.getName()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private String findRequesterByTarget(String targetName) {
|
||||
for (Map.Entry<String, String> entry : crossServerTpaRequests.entrySet()) {
|
||||
if (targetName.equalsIgnoreCase(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String findRequesterKey(String requesterName) {
|
||||
for (String key : crossServerTpaRequests.keySet()) {
|
||||
if (key.equalsIgnoreCase(requesterName)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,14 @@ public class WarpManager {
|
||||
public WarpManager(TeleportSuite plugin) { this.plugin = plugin; }
|
||||
|
||||
public void setWarp(Player player, String name, String permission) {
|
||||
TeleportLocation loc = new TeleportLocation(player.getLocation(), plugin.getConfigManager().getServerName());
|
||||
// Snapshot the location at the exact moment /setwarp is called
|
||||
TeleportLocation loc = new TeleportLocation(
|
||||
player.getLocation().clone(),
|
||||
plugin.getConfigManager().getServerName());
|
||||
plugin.getDatabaseManager().saveWarp(name, loc, player.getUniqueId(), permission);
|
||||
plugin.getLogger().info("Warp '" + name + "' gesetzt: "
|
||||
+ loc.getWorld() + " " + loc.getX() + "/" + loc.getY() + "/" + loc.getZ()
|
||||
+ " von " + player.getName());
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("warp-set", "name", name));
|
||||
}
|
||||
|
||||
@@ -26,7 +32,11 @@ public class WarpManager {
|
||||
player.sendMessage(plugin.getConfigManager().getMessage("no-permission"));
|
||||
return;
|
||||
}
|
||||
plugin.getTeleportManager().teleport(player, warp.getLocation());
|
||||
TeleportLocation dest = warp.getLocation();
|
||||
plugin.getLogger().info("Warp '" + name + "' geladen: "
|
||||
+ dest.getWorld() + " " + dest.getX() + "/" + dest.getY() + "/" + dest.getZ()
|
||||
+ " -> Spieler " + player.getName());
|
||||
plugin.getTeleportManager().teleport(player, dest);
|
||||
}
|
||||
|
||||
public boolean deleteWarp(String name) {
|
||||
|
||||
5
src/main/resources/bungee.yml
Normal file
5
src/main/resources/bungee.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: TeleportSuite-Bungee
|
||||
main: de.teleportsuite.bungee.TeleportSuiteBungee
|
||||
version: 1.0
|
||||
author: TeleportSuite
|
||||
description: BungeeCord-Companion fuer TeleportSuite
|
||||
@@ -15,9 +15,10 @@ database:
|
||||
file: teleportsuite.db
|
||||
|
||||
# BungeeCord Einstellungen
|
||||
# WICHTIG: server-name muss dem Namen dieses Servers in der BungeeCord config.yml entsprechen!
|
||||
bungee:
|
||||
enabled: false
|
||||
server-name: "survival" # Name dieses Servers im BungeeCord-Netz
|
||||
enabled: true
|
||||
server-name: "survival" # Name dieses Servers im BungeeCord-Netz (z.B. "lobby", "survival", "creative")
|
||||
|
||||
# Teleport Einstellungen
|
||||
teleport:
|
||||
@@ -70,6 +71,7 @@ messages:
|
||||
death-no-location: "&cKein Todesort gespeichert."
|
||||
no-permission: "&cDazu hast du keine Berechtigung."
|
||||
player-not-found: "&cSpieler &6{player}&c nicht gefunden."
|
||||
tpa-request-not-found: "&cKeine offene Teleportanfrage gefunden."
|
||||
portal-created: "&aPortal &6{name}&a erstellt!"
|
||||
portal-deleted: "&cPortal &6{name}&c gelöscht."
|
||||
savepoint-set: "&aSavepoint &6{name}&a gesetzt!"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: TeleportSuite
|
||||
version: 1.0.0
|
||||
version: 1.0.4
|
||||
main: de.teleportsuite.TeleportSuite
|
||||
api-version: 1.20
|
||||
description: BungeeCord-fähiges Teleport-Komplettpaket
|
||||
|
||||
Reference in New Issue
Block a user