17 Commits
1.1.0 ... 1.1.3

Author SHA1 Message Date
f7f0a0d532 Upload pom.xml via GUI 2026-02-26 23:45:15 +00:00
35dded973b Update from Git Manager GUI 2026-02-26 11:07:51 +01:00
43dac083d4 Upload pom.xml via GUI 2026-02-26 10:07:50 +00:00
9c1b980388 Update from Git Manager GUI 2026-02-06 07:38:13 +01:00
983ca72aaa Upload pom.xml via GUI 2026-02-06 06:38:11 +00:00
eed33a4bd7 README.md aktualisiert 2026-02-05 21:52:03 +00:00
0ede50287f Upload pom.xml via GUI 2026-02-05 21:49:12 +00:00
6b0d6fa460 Update from Git Manager GUI 2026-02-05 22:49:10 +01:00
d219278689 Upload pom.xml via GUI 2026-02-05 21:44:23 +00:00
1bea420d24 Update from Git Manager GUI 2026-02-05 22:44:21 +01:00
42cd51aa35 Upload file readme.md via GUI 2026-02-05 22:44:20 +01:00
ef27111cad spigot-plugin.txt gelöscht 2026-02-02 16:54:48 +00:00
cac8d9287d Upload file spigot-plugin.txt via GUI 2026-02-02 17:54:39 +01:00
4bb1086534 spigot-plugin.txt gelöscht 2026-02-02 01:50:52 +00:00
e8db790b74 Upload file spigot-plugin.txt via GUI 2026-02-02 02:50:39 +01:00
fe48d0e9f3 Upload pom.xml via GUI 2026-02-02 01:32:26 +00:00
2bf1c72971 Upload pom.xml via GUI 2026-02-02 01:16:35 +00:00
50 changed files with 2593 additions and 719 deletions

335
README.md
View File

@@ -4,15 +4,16 @@
<img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby"> <img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby">
</p> </p>
Ein umfassendes Lobby-Plugin für Minecraft Server (Paper/Spigot 1.21+) mit modularem Aufbau, High-End NPC-System, umfangreichen Sicherheitsfunktionen und voller Konfigurierbarkeit. Ein umfassendes, modulares Lobby-Plugin für Minecraft Server (Paper/Spigot 1.21+) mit High-End NPC-System, umfangreichen Sicherheitsfunktionen, Soccer-Modul, Parkour-System und voller Konfigurierbarkeit.
![Minecraft](https://img.shields.io/badge/Minecraft-1.21+-green) ![Minecraft](https://img.shields.io/badge/Minecraft-1.21+-green)
![Java](https://img.shields.io/badge/Java-21+-orange) ![Java](https://img.shields.io/badge/Java-21+-orange)
![License](https://img.shields.io/badge/License-Proprietary-red) ![License](https://img.shields.io/badge/License-Proprietary-red)
![Version](https://img.shields.io/badge/Version-1.1.0-blue)
--- ---
## Lizenz und Nutzungsbedingungen ## 🎯 Lizenz und Nutzungsbedingungen
**ALLE RECHTE VORBEHALTEN** **ALLE RECHTE VORBEHALTEN**
@@ -28,68 +29,245 @@ Bei Verstoß gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
--- ---
## Features ## Features
### 🤖 High-End NPC & ArmorStand System ### 🤖 High-End NPC & ArmorStand System
- **Conversation Manager** - Komplexe Dialoge zwischen NPCs mit Sprechblasen und Sound-Effekten. - **Conversation Manager** - Komplexe Dialoge zwischen NPCs mit Sprechblasen und Sound-Effekten
- **Dynamic KI** - NPCs reagieren auf Tageszeit (Fackel nachts) und ziehen bei Annäherung von Spielern das Schwert. - **Dynamic KI** - NPCs reagieren auf Tageszeit (Fackel nachts) und ziehen bei Annäherung von Spielern das Schwert
- **LookAt-Logik** - NPCs verfolgen flüssig die Kopfbewegungen von Spielern in der Nähe. - **LookAt-Logik** - NPCs verfolgen flüssig die Kopfbewegungen von Spielern in der Nähe
- **Command Binding** - Binde Spieler-, Konsolen- oder Bungee-Befehle an NPC-Slots (0-9). - **Command Binding** - Binde Spieler-, Konsolen- oder Bungee-Befehle an NPC-Slots (0-9)
- **Status-Backup** - Automatisches Speichern von NPC-Namen via PersistentDataContainer & Tags. - **Status-Backup** - Automatisches Speichern von NPC-Namen via PersistentDataContainer & Tags
### ⚽ Soccer/Fußball-System
- **Interaktiver Soccer-Ball** - Physics-basierter Ball mit realistischer Physik
- **Dribbling** - Ball folgt Spielern automatisch in der Nähe
- **Kick-Mechanik** - Schieße den Ball durch Schlagen oder Angriff
- **Wall-Bounce** - Realistische Wand-Abpraller mit Sound-Effekten
- **Partikel-System** - Geschwindigkeits-abhängige Partikel (SONIC_BOOM, CRIT, SMOKE)
- **Auto-Respawn** - Automatischer Respawn bei Inaktivität oder Void-Fall
- **Anti-Duplikat-System** - Verhindert mehrfache Ball-Instanzen
### 🏃 Parkour-System
- **Unsichtbare Checkpoints** - Partikel-basierte Checkpoint-Markierungen
- **Timer-System** - Präzise Zeitmessung mit ActionBar-Anzeige
- **Bestenliste** - Persistente Top-10 Bestzeiten-Speicherung
- **NPC-Trainer** - Interaktive NPCs zum Starten des Parkours
- **Fail-Safe** - Teleport zum letzten Checkpoint bei Fehler
### 🌍 Lobby-Management ### 🌍 Lobby-Management
- **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn. - **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn
- **Portal-System** - BungeeCord-Portale für nahtlose Server-Wechsel. - **Portal-System** - BungeeCord-Portale für nahtlose Server-Wechsel mit Partikel-Effekten
- **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus. - **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus
- **Double-Jump** - Konfigurierbarer Doppelsprung mit Cooldown und Partikeln. - **Double-Jump** - Konfigurierbarer Doppelsprung mit Cooldown und Partikeln
- **Worldborder** - Unsichtbare Lobby-Begrenzung (Circle/Square)
- **Intro-Tour** - Cinematic Kamera-Tour für neue Spieler
### 🛡️ Sicherheit & Protection ### 🛡️ Sicherheit & Protection
- **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen via proxycheck.io API. - **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen via proxycheck.io API
- **Country-Blocker** - Geo-IP Filter (Whitelist/Blacklist für Länder). - **Country-Blocker** - Geo-IP Filter (Whitelist/Blacklist für Länder)
- **Maintenance** - Vollständiger Wartungsmodus mit Whitelist-Funktion. - **Maintenance-Modus** - Vollständiger Wartungsmodus mit Whitelist-Funktion
- **World-Protection** - Schutz vor Griefing, Hunger, Fallschaden und PvP. - **World-Protection** - Schutz vor Griefing, Hunger, Fallschaden und PvP
- **Anti-Grief** - Verhindert Block-Break, Item-Drop und Explosionen
- **Security-Module** - Umfassende Sicherheitsprüfungen beim Join
### 📊 Visuelle Elemente ### 📊 Visuelle Elemente
- **Scoreboard & Tablist** - Vollständig animiert mit PlaceholderAPI-Support. - **Scoreboard** - Vollständig animiert mit PlaceholderAPI-Support
- **BossBar & ActionBar** - Rotierende Nachrichten und permanente Status-Anzeigen. - **Tablist** - Dynamische Header/Footer mit Permissions-Anzeige
- **Hologramme** - Einfache Erstellung von Text-Displays in der Lobby. - **BossBar** - Rotierende Nachrichten mit Farb-Effekten
- **ActionBar** - Typewriter-Effekt für permanente Status-Anzeigen
- **Hologramme** - Text-Displays mit flexibler Positionierung
- **MapArt** - Bild-Generator für Item-Frames aus URLs
### 🎮 Item-System
- **Compass** - Server-Switcher GUI
- **Player-Hider** - Sichtbarkeits-Toggle für andere Spieler
- **Gadgets** - Konfigurierbares Item-System mit Custom-Slots
- **Player-Inspector** - Detaillierte Spieler-Informationen per Click
--- ---
## Befehle ## 📋 Befehle
### Haupt-Commands
| Befehl | Beschreibung | Berechtigung | | Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------| |--------|--------------|--------------|
| `/nexuslobby` | Hauptbefehl (reload, setspawn, silentjoin) | `nexuslobby.admin` | | `/nexuslobby reload` | Lädt das Plugin neu | `nexuslobby.admin` |
| `/nexuscmd` | NPC Command/Dialog Verwaltung (aliases: `ncmd`, `conv`) | `nexuslobby.armorstand.cmd` | | `/nexuslobby setspawn` | Setzt den Lobby-Spawn | `nexuslobby.admin` |
| `/nexustools` | NPC Editor GUI (Rotation, KI, Sichtbarkeit) | `nexuslobby.armorstand.use` | | `/nexuslobby silentjoin <on\|off>` | Versteckter Join/Quit | `nexuslobby.admin` |
| `/build` | Aktiviert/Deaktiviert den Baumodus | `nexuslobby.build` | | `/nexuslobby sb <on\|off\|admin\|spieler>` | Scoreboard-Kontrolle | `nexuslobby.admin` |
| `/maintenance` | Schaltet den Wartungsmodus (on/off) | `nexuslobby.maintenance` | | `/spawn` | Teleport zum Spawn | `nexuslobby.spawn` |
| `/portal` | Verwaltung der Server-Portale | `nexuslobby.portal` |
| `/holo` | Erstellt oder löscht Text-Hologramme | `nexuslobby.hologram` | ### NPC & ArmorStand
| `/mapart` | Erstellt Bilder aus URLs auf Karten-Rahmen | `nexuslobby.mapart` |
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/nexustools` | NPC Editor GUI (Rotation, KI) | `nexuslobby.armorstand.use` |
| `/nexuscmd add <slot> <prio> <type> [cmd]` | Bindet Command an NPC | `nexuslobby.armorstand.cmd` |
| `/nexuscmd remove <slot\|all>` | Entfernt Commands | `nexuslobby.armorstand.cmd` |
| `/nexuscmd conv select1-4` | Wählt NPCs für Dialog | `nexuslobby.armorstand.cmd` |
| `/nexuscmd conv link <id>` | Verknüpft Dialog | `nexuslobby.armorstand.cmd` |
| `/nexuscmd say <text>` | NPC-Sprechblase | `nexuslobby.armorstand.cmd` |
### Parkour
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/nexuslobby parkour setstart` | Markiert Start-NPC | `nexuslobby.admin` |
| `/nexuslobby parkour setcheckpoint <n>` | Setzt Checkpoint | `nexuslobby.admin` |
| `/nexuslobby parkour setfinish` | Setzt Ziel | `nexuslobby.admin` |
| `/nexuslobby parkour reset` | Löscht eigene Zeit | `nexuslobby.admin` |
| `/nexuslobby parkour clear` | Löscht alle Zeiten | `nexuslobby.admin` |
| `/setstart` | Alias für setstart | `nexuslobby.admin` |
| `/setcheckpoint` | Alias für setcheckpoint | `nexuslobby.admin` |
| `/setfinish` | Alias für setfinish | `nexuslobby.admin` |
### Soccer/Fußball
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/nexuslobby ball setspawn` | Setzt Ball-Spawn | `nexuslobby.admin` |
| `/nexuslobby ball respawn` | Spawnt Ball neu | `nexuslobby.admin` |
| `/nexuslobby ball remove` | Entfernt Ball | `nexuslobby.admin` |
### Portal & Server
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/portal create <name> <type>` | Erstellt Portal | `nexuslobby.portal` |
| `/portal delete <name>` | Löscht Portal | `nexuslobby.portal` |
| `/portal list` | Zeigt alle Portale | `nexuslobby.portal` |
| `/giveportalwand` | Portal-Werkzeug | `nexuslobby.portal.give` |
| `/serverswitcher` | Öffnet Server-GUI | `nexuslobby.serverswitcher` |
### Sonstiges
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/build` | Build-Modus toggle | `nexuslobby.build` |
| `/maintenance <on\|off>` | Wartungsmodus | `nexuslobby.maintenance` |
| `/settings` | Lobby-Einstellungen (Gamerules) | `nexuslobby.admin` |
| `/holo create <id> [text]` | Erstellt Hologramm | `nexuslobby.hologram` |
| `/holo delete <id>` | Löscht Hologramm | `nexuslobby.hologram` |
| `/mapart <URL> <BxH>` | Erstellt MapArt | `nexuslobby.mapart` |
| `/intro <add\|clear\|start>` | Intro-Tour-Verwaltung | `nexuslobby.admin` |
| `/border <circle\|square\|disable>` | Worldborder-Setup | `nexuslobby.admin` |
--- ---
## Konfiguration (Auszug) ## ⚙️ Konfiguration
### config.yml (Auszug)
```yaml
# Spawn-Einstellungen
spawn:
world: "world"
x: 0.5
y: 64.0
z: 0.5
yaw: 0.0
pitch: 0.0
# Lobby-Einstellungen
lobby:
allow-fly: false
pvp-enabled: false
build-enabled: false
default-gamemode: Adventure
clear-inventory-on-join: true
# Server-Mapping (für Status-Check)
servers:
survival:
ip: "127.0.0.1"
port: 25566
skyblock:
ip: "127.0.0.1"
port: 25567
# Ball/Soccer-Einstellungen
ball:
respawn_delay: 60 # Sekunden bis Auto-Respawn
# Worldborder
worldborder:
enabled: true
type: "SQUARE" # SQUARE oder CIRCLE
radius: 50.0
```
### conversations.yml ### conversations.yml
```yaml ```yaml
conversations: conversations:
willkommen: willkommen:
dialogue: dialogue:
- "&eWächter: &7Willkommen auf dem Server!" - "&eWächter: &7Willkommen auf dem Server!"
- "&aBürger: &7Schön, dass du da bist!" - "&aBürger: &7Schön, dass du da bist!"
- "&eWächter: &7Viel Spaß beim Erkunden!"
quest_dialog:
dialogue:
- "&6Quest-Geber: &7Hast du meine Quest erledigt?"
- "&aHeld: &7Ja, ich habe alle Monster besiegt!"
links: links:
UUID-NPC1: # UUID des ersten NPCs
partner: UUID-NPC2 "550e8400-e29b-41d4-a716-446655440000":
partner: "550e8400-e29b-41d4-a716-446655440001" # UUID des zweiten NPCs
dialog: willkommen dialog: willkommen
automated: true # Startet automatisch
```
### visuals.yml (Auszug)
```yaml
# ActionBar-Nachrichten
actionbar:
enabled: true
speed: 3
messages:
- "&6&lWillkommen &8» &eauf unserem Server!"
- "&b&lViel Spaß &8» &7in der Lobby!"
# BossBar
bossbar:
enabled: true
interval: 40
messages:
- text: "&6&lNexusLobby &8» &eVersion 1.1.0"
color: "YELLOW"
``` ```
--- ---
## Berechtigungen ## 🌐 Mehrsprachigkeit & Texte
Alle Nachrichten, Hilfetexte und Fehler werden zentral über die Datei `lang.yml` im Ordner `src/main/resources` verwaltet. Dort kannst du für jede Sprache (z.B. Deutsch und Englisch) die Texte pflegen und beliebig erweitern.
**Beispiel für lang.yml:**
```yaml
welcome:
de: "Willkommen auf dem Server!"
en: "Welcome to the server!"
no_permission:
de: "§cKeine Berechtigung."
en: "§cNo permission."
```
**Sprache umstellen:**
Im Code kann die Sprache mit `LangManager.setLanguage("en")` gewechselt werden. Standard ist Deutsch (`de`).
**Texte ingame nutzen:**
Alle Texte werden im Plugin mit `LangManager.get("key")` abgerufen und sind direkt ingame sichtbar. Änderungen in der lang.yml wirken nach einem Reload sofort.
---
## 🔐 Berechtigungen
### Admin-Berechtigungen
| Berechtigung | Beschreibung | | Berechtigung | Beschreibung |
|--------------|--------------| |--------------|--------------|
@@ -98,6 +276,18 @@ links:
| `nexuslobby.armorstand.use` | Zugriff auf die ArmorStand-Editor GUI | | `nexuslobby.armorstand.use` | Zugriff auf die ArmorStand-Editor GUI |
| `nexuslobby.build` | Berechtigung für den Baumodus | | `nexuslobby.build` | Berechtigung für den Baumodus |
| `nexuslobby.portal` | Portale erstellen und löschen | | `nexuslobby.portal` | Portale erstellen und löschen |
| `nexuslobby.portal.give` | Portal-Werkzeug erhalten |
| `nexuslobby.hologram` | Hologramme verwalten |
| `nexuslobby.mapart` | MapArt erstellen |
| `nexuslobby.maintenance` | Wartungsmodus togglen |
### Spieler-Berechtigungen
| Berechtigung | Beschreibung |
|--------------|--------------|
| `nexuslobby.spawn` | Spawn-Command nutzen |
| `nexuslobby.serverswitcher` | Server-Switcher GUI öffnen |
| `nexuslobby.scoreboard.admin` | Admin-Scoreboard sehen |
### Bypass-Berechtigungen ### Bypass-Berechtigungen
@@ -105,16 +295,89 @@ links:
|--------------|--------------| |--------------|--------------|
| `nexuslobby.bypass.maintenance` | Server trotz Wartungsmodus betreten | | `nexuslobby.bypass.maintenance` | Server trotz Wartungsmodus betreten |
| `nexuslobby.bypass.vpn` | VPN-Check überspringen | | `nexuslobby.bypass.vpn` | VPN-Check überspringen |
| `nexuslobby.bypass.country` | Country-Check überspringen |
--- ---
## Support & Kontakt ## 🔧 Technische Details
- **Wiki:** Ausführliche Dokumentation der Module im [Wiki](../../../wiki). ### Systemanforderungen
- **Bug-Reports:** Erstelle ein [Issue](../../../issues) bei technischen Problemen. - **Minecraft**: Paper/Spigot 1.21+ (Spigot-kompatibel, Paper empfohlen)
- **Java**: Java 21 oder höher
- **Dependencies**: LuckPerms, PlaceholderAPI (optional)
### Verwendete Technologien
- **Build-Tool**: Maven 3.9+
- **JSON-Library**: Gson 2.10.1 (gegenüber json-simple 1.1.1 aktualisiert)
- **BungeeCord API**: Für Server-Wechsel und Messaging
- **PlaceholderAPI**: Für dynamische Platzhalter
### Code-Qualität
- ✅ Moderne Java 21 Features (Switch-Expressions, Pattern Matching)
- ✅ Proper Error-Handling (Logger statt printStackTrace)
- ✅ Memory-Management (Tasks & Listeners werden sauber disposed)
- ✅ Config-Validierung mit Default-Werten
- ✅ Optimiertes JSON-Parsing mit Gson
- ✅ Spigot-kompatible BungeeCord Chat API
### Performance-Optimierungen
- **Async-Processing** für API-Calls (Update-Check, VPN-Check)
- **Caching** für Spieler-Daten und Config-Werte
- **Optimierte Entity-Queries** (nur relevante Welten)
- **Effiziente Scheduler-Nutzung** (Tasks werden gecancelt)
--- ---
**Copyright (c) 2026 - M_Viper - Alle Rechte vorbehalten** ## 📦 Installation
1. **Download** der neuesten `NexusLobby-1.1.0.jar` aus den [Releases](../../releases)
2. **Upload** der JAR-Datei in den `/plugins/` Ordner
3. **Server-Neustart** durchführen
4. **Konfiguration** anpassen (`config.yml`, `visuals.yml`, etc.)
5. **Permissions** mit LuckPerms oder einem anderen Permissions-Plugin setzen
6. **(Optional)** PlaceholderAPI installieren für erweiterte Platzhalter
---
## 🐛 Bekannte Issues & Lösungen
### Ball spawnt mehrfach
- **Lösung**: Nutze `/nexuslobby ball remove` gefolgt von `/nexuslobby ball setspawn`
- Das Anti-Duplikat-System sollte dies automatisch verhindern
### NPCs schauen nicht zu Spielern
- **Lösung**: Stelle sicher, dass der NPC mit `/nexustools` das LookAt-Feature aktiviert hat
- Prüfe, ob `ArmorStandLookAtModule` in den Logs geladen wird
### Server-Checker zeigt falsche Status
- **Lösung**: Überprüfe die IP/Port-Einstellungen in `config.yml` unter `servers:`
- Stelle sicher, dass die Server erreichbar sind (Firewall, Ports)
---
## 📚 Support & Dokumentation
- **Wiki**: Ausführliche Dokumentation im [Wiki](../../wiki)
- **Bug-Reports**: Erstelle ein [Issue](../../issues) bei technischen Problemen
- **Feature-Requests**: Vorschläge über [Issues](../../issues) mit Label "enhancement"
---
## 📝 Changelog
### Version 1.1.0 (Februar 2026)
- ✨ Soccer/Fußball-System mit realistischer Physik
- ✨ Parkour-System mit Bestenliste und Checkpoints
- ✨ Player-Inspector Modul
- ✨ Config-Validierung mit Auto-Defaults
- 🔧 JSON-Library auf Gson 2.10.1 aktualisiert
- 🔧 Alle printStackTrace() durch Logger ersetzt
- 🔧 Memory-Management verbessert (Tasks & Listeners)
- 🔧 UpdateChecker mit proper JSON-Parsing
- 🐛 Diverse Bug-Fixes und Performance-Optimierungen
---
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt. Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.

12
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>de.nexuslobby</groupId> <groupId>de.nexuslobby</groupId>
<artifactId>NexusLobby</artifactId> <artifactId>NexusLobby</artifactId>
<version>1.1.0</version> <version>1.1.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>NexusLobby</name> <name>NexusLobby</name>
@@ -57,9 +57,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.googlecode.json-simple</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>json-simple</artifactId> <artifactId>gson</artifactId>
<version>1.1.1</version> <version>2.10.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -90,8 +90,8 @@
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
<relocations> <relocations>
<relocation> <relocation>
<pattern>org.json.simple</pattern> <pattern>com.google.gson</pattern>
<shadedPattern>de.nexuslobby.libs.json</shadedPattern> <shadedPattern>de.nexuslobby.libs.gson</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
<filters> <filters>

View File

@@ -23,7 +23,7 @@ import de.nexuslobby.modules.border.BorderModule;
import de.nexuslobby.modules.parkour.ParkourManager; import de.nexuslobby.modules.parkour.ParkourManager;
import de.nexuslobby.modules.parkour.ParkourListener; import de.nexuslobby.modules.parkour.ParkourListener;
import de.nexuslobby.modules.player.PlayerInspectModule; import de.nexuslobby.modules.player.PlayerInspectModule;
import de.nexuslobby.modules.ball.SoccerModule; // NEU import de.nexuslobby.modules.ball.SoccerModule;
import de.nexuslobby.utils.*; import de.nexuslobby.utils.*;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
@@ -68,7 +68,8 @@ public class NexusLobby extends JavaPlugin implements Listener {
private IntroModule introModule; private IntroModule introModule;
private BorderModule borderModule; private BorderModule borderModule;
private ParkourManager parkourManager; private ParkourManager parkourManager;
private SoccerModule soccerModule; // NEU private SoccerModule soccerModule;
private ArmorStandLookAtModule armorStandLookAtModule;
private ConversationManager conversationManager; private ConversationManager conversationManager;
@@ -96,15 +97,28 @@ public class NexusLobby extends JavaPlugin implements Listener {
return parkourManager; return parkourManager;
} }
public SoccerModule getSoccerModule() { // NEU public SoccerModule getSoccerModule() {
return soccerModule; return soccerModule;
} }
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; getLogger().info("");
getLogger().info("[NexusLobby] _ __ __ __ __ ");
getLogger().info("[NexusLobby] / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __");
getLogger().info("[NexusLobby] / |/ / _ \\| |/_/ / / / ___/ / / __ \\ / __ \\/ __ \\/ / / /");
getLogger().info("[NexusLobby] / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ / ");
getLogger().info("[NexusLobby] /_/ |_/\\___/_/|_|\\__,_/____/_____/\\____/_.___/_.___/\\__, / ");
getLogger().info("[NexusLobby] /____/ ");
getLogger().info("[NexusLobby] ");
getLogger().info("[NexusLobby] NexusLobby Plugin aktiviert! Version: " + getDescription().getVersion());
instance = this;
initCustomConfigs(); initCustomConfigs();
validateConfig();
// Lade die Sprachdatei
LangManager.load(this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
moduleManager = new ModuleManager(this); moduleManager = new ModuleManager(this);
@@ -122,7 +136,7 @@ public class NexusLobby extends JavaPlugin implements Listener {
ServerChecker.startGlobalChecker(); ServerChecker.startGlobalChecker();
new ArmorStandLookAtModule(); armorStandLookAtModule = new ArmorStandLookAtModule();
startAutoConversationTimer(); startAutoConversationTimer();
@@ -133,8 +147,6 @@ public class NexusLobby extends JavaPlugin implements Listener {
registerCommands(); registerCommands();
checkUpdates(); checkUpdates();
getLogger().info("NexusLobby v" + getDescription().getVersion() + " wurde erfolgreich gestartet.");
} }
private void startAutoConversationTimer() { private void startAutoConversationTimer() {
@@ -181,7 +193,10 @@ public class NexusLobby extends JavaPlugin implements Listener {
} }
ServerChecker.startGlobalChecker(); ServerChecker.startGlobalChecker();
new ArmorStandLookAtModule(); // FIX: ArmorStandLookAtModule als Feld tracken. Bukkit.getScheduler().cancelTasks()
// am Anfang von reloadPlugin() cancelt den alten Task bereits hier nur
// neu starten und Referenz aktualisieren, damit kein doppelter Task läuft.
armorStandLookAtModule = new ArmorStandLookAtModule();
startAutoConversationTimer(); startAutoConversationTimer();
getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen."); getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen.");
@@ -242,8 +257,8 @@ public class NexusLobby extends JavaPlugin implements Listener {
moduleManager.registerModule(new PlayerInspectModule()); moduleManager.registerModule(new PlayerInspectModule());
// Soccer Modul registrieren // Soccer Modul registrieren
this.soccerModule = new SoccerModule(); // NEU this.soccerModule = new SoccerModule();
moduleManager.registerModule(this.soccerModule); // NEU moduleManager.registerModule(this.soccerModule);
this.portalManager = new PortalManager(this); this.portalManager = new PortalManager(this);
moduleManager.registerModule(portalManager); moduleManager.registerModule(portalManager);
@@ -262,6 +277,11 @@ public class NexusLobby extends JavaPlugin implements Listener {
getServer().getPluginManager().registerEvents(new NPCClickListener(), this); getServer().getPluginManager().registerEvents(new NPCClickListener(), this);
} }
private void validateConfig() {
ConfigValidator validator = new ConfigValidator(this, getConfig());
validator.validate();
}
public class NPCClickListener implements Listener { public class NPCClickListener implements Listener {
@EventHandler @EventHandler
public void onNPCClick(PlayerInteractAtEntityEvent event) { public void onNPCClick(PlayerInteractAtEntityEvent event) {
@@ -280,7 +300,9 @@ public class NexusLobby extends JavaPlugin implements Listener {
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
event.setJoinMessage(null); if (silentPlayers.contains(player.getUniqueId())) {
event.setJoinMessage(null);
}
teleportToSpawn(player); teleportToSpawn(player);
@@ -289,7 +311,7 @@ public class NexusLobby extends JavaPlugin implements Listener {
BuildCommand.removePlayerFromBuildMode(player); BuildCommand.removePlayerFromBuildMode(player);
String defaultGmName = getConfig().getString("default-gamemode", "ADVENTURE"); String defaultGmName = getConfig().getString("lobby.default-gamemode", "Adventure");
try { try {
player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase())); player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase()));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -297,17 +319,15 @@ public class NexusLobby extends JavaPlugin implements Listener {
} }
if (player.hasPermission("nexuslobby.admin") && updateAvailable) { if (player.hasPermission("nexuslobby.admin") && updateAvailable) {
player.sendMessage(" "); player.sendMessage("");
player.sendMessage("§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("update_available"));
player.sendMessage("§8» §7Version: §c" + getDescription().getVersion() + " §8-> §a" + latestVersion); player.sendMessage(de.nexuslobby.utils.LangManager.get("update_version").replace("{old}", getDescription().getVersion()).replace("{new}", latestVersion));
TextComponent link = new TextComponent(de.nexuslobby.utils.LangManager.get("update_download_link"));
TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen.");
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases")); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases"));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder("§7Öffnet die Release-Seite").create())); new ComponentBuilder(de.nexuslobby.utils.LangManager.get("update_download_hover")).create()));
player.spigot().sendMessage(link); player.spigot().sendMessage(link);
player.sendMessage(" "); player.sendMessage("");
} }
} }
@@ -344,6 +364,10 @@ public class NexusLobby extends JavaPlugin implements Listener {
visualsFile = new File(getDataFolder(), "visuals.yml"); visualsFile = new File(getDataFolder(), "visuals.yml");
if (!visualsFile.exists()) saveResource("visuals.yml", false); if (!visualsFile.exists()) saveResource("visuals.yml", false);
// lang.yml automatisch erstellen, falls nicht vorhanden
File langFile = new File(getDataFolder(), "lang.yml");
if (!langFile.exists()) saveResource("lang.yml", false);
reloadVisualsConfig(); reloadVisualsConfig();
Config.load(); Config.load();
} }
@@ -360,10 +384,23 @@ public class NexusLobby extends JavaPlugin implements Listener {
@Override @Override
public void onDisable() { public void onDisable() {
// Cancle alle Scheduler-Tasks
Bukkit.getScheduler().cancelTasks(this);
// Stoppe spezifische Tasks
ServerChecker.stopGlobalChecker();
// Unregister alle Event-Listener
org.bukkit.event.HandlerList.unregisterAll((org.bukkit.plugin.Plugin) this);
// Schließe BungeeCord Channel
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
// Disable alle Module (inkl. eigenes Cleanup)
if (moduleManager != null) { if (moduleManager != null) {
moduleManager.disableAll(); moduleManager.disableAll();
} }
getLogger().info("NexusLobby deaktiviert."); getLogger().info("NexusLobby deaktiviert.");
} }
@@ -410,6 +447,21 @@ public class NexusLobby extends JavaPlugin implements Listener {
getCommand("spawn").setTabCompleter(tabCompleter); getCommand("spawn").setTabCompleter(tabCompleter);
} }
if (getCommand("setstart") != null) {
getCommand("setstart").setExecutor(nexusCommand);
getCommand("setstart").setTabCompleter(tabCompleter);
}
if (getCommand("setcheckpoint") != null) {
getCommand("setcheckpoint").setExecutor(nexusCommand);
getCommand("setcheckpoint").setTabCompleter(tabCompleter);
}
if (getCommand("setfinish") != null) {
getCommand("setfinish").setExecutor(nexusCommand);
getCommand("setfinish").setTabCompleter(tabCompleter);
}
if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter); if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter);
if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter); if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter);
@@ -417,6 +469,11 @@ public class NexusLobby extends JavaPlugin implements Listener {
getCommand("border").setExecutor(new BorderCommand()); getCommand("border").setExecutor(new BorderCommand());
getCommand("border").setTabCompleter(tabCompleter); getCommand("border").setTabCompleter(tabCompleter);
} }
if (getCommand("serverswitcher") != null) {
ServerSwitcherListener serverSwitcher = new ServerSwitcherListener();
getCommand("serverswitcher").setExecutor(serverSwitcher);
}
} }
public class NexusLobbyExpansion extends PlaceholderExpansion { public class NexusLobbyExpansion extends PlaceholderExpansion {

View File

@@ -25,7 +25,6 @@ public class ModuleManager {
*/ */
public void enableAll() { public void enableAll() {
for (Module module : modules) { for (Module module : modules) {
plugin.getLogger().info("[NexusLobby] Enabling module: " + module.getName());
module.onEnable(); module.onEnable();
} }
} }

View File

@@ -39,7 +39,7 @@ public class BuildCommand implements CommandExecutor {
} }
// Gamemode zurücksetzen // Gamemode zurücksetzen
String defaultGmName = NexusLobby.getInstance().getConfig().getString("default-gamemode", "ADVENTURE"); String defaultGmName = NexusLobby.getInstance().getConfig().getString("lobby.default-gamemode", "Adventure");
try { try {
GameMode gm = GameMode.valueOf(defaultGmName.toUpperCase()); GameMode gm = GameMode.valueOf(defaultGmName.toUpperCase());
player.setGameMode(gm); player.setGameMode(gm);

View File

@@ -33,6 +33,12 @@ public class GivePortalToolCommand implements CommandExecutor {
// Erstelle das Item // Erstelle das Item
ItemStack wand = new ItemStack(Material.BLAZE_ROD); ItemStack wand = new ItemStack(Material.BLAZE_ROD);
ItemMeta meta = wand.getItemMeta(); ItemMeta meta = wand.getItemMeta();
if (meta == null) {
p.getInventory().addItem(wand);
p.sendMessage(plugin.getName() + " §aDu hast das Portal-Werkzeug erhalten!");
p.playSound(p.getLocation(), org.bukkit.Sound.ENTITY_ITEM_PICKUP, 1.0f, 1.0f);
return true;
}
// Design des Items // Design des Items
meta.setDisplayName("§cPortal-Werkzeug"); meta.setDisplayName("§cPortal-Werkzeug");

View File

@@ -28,65 +28,111 @@ public class LobbyTabCompleter implements TabCompleter {
List<String> suggestions = new ArrayList<>(); List<String> suggestions = new ArrayList<>();
String cmdName = command.getName().toLowerCase(); String cmdName = command.getName().toLowerCase();
// --- NexusLobby Hauptbefehl (/nexus) --- // ── NexusLobby Hauptbefehl (/nexus oder /nexuslobby) ─────────────────
if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) { if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) {
// /nexuslobby <?>
if (args.length == 1) { if (args.length == 1) {
if (sender.hasPermission("nexuslobby.admin")) { if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList("reload", "setspawn", "silentjoin", "parkour", "ball")); // NEU: ball suggestions.addAll(Arrays.asList(
"reload", "setspawn", "silentjoin", "parkour", "ball"
));
} }
suggestions.add("sb"); suggestions.add("sb");
// /nexuslobby <sub> <?>
} else if (args.length == 2) { } else if (args.length == 2) {
if (args[0].equalsIgnoreCase("sb")) { switch (args[0].toLowerCase()) {
suggestions.addAll(Arrays.asList("on", "off")); case "sb" -> {
if (sender.hasPermission("nexuslobby.scoreboard.admin")) { suggestions.addAll(Arrays.asList("on", "off"));
suggestions.addAll(Arrays.asList("admin", "spieler")); if (sender.hasPermission("nexuslobby.scoreboard.admin"))
suggestions.addAll(Arrays.asList("admin", "spieler"));
} }
} else if (args[0].equalsIgnoreCase("silentjoin")) { case "silentjoin" -> suggestions.addAll(Arrays.asList("on", "off"));
suggestions.addAll(Arrays.asList("on", "off")); case "parkour" -> suggestions.addAll(Arrays.asList(
} else if (args[0].equalsIgnoreCase("parkour")) { "setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall"
suggestions.addAll(Arrays.asList("setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall")); ));
} else if (args[0].equalsIgnoreCase("ball")) { // NEU: Ball Subcommands case "ball" -> {
if (sender.hasPermission("nexuslobby.admin")) { if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList("setspawn", "reload")); suggestions.addAll(Arrays.asList(
"setspawn", "respawn", "remove", "goal", "score"
));
}
} }
} }
// /nexuslobby <sub> <sub2> <?>
} else if (args.length == 3) { } else if (args.length == 3) {
if (args[0].equalsIgnoreCase("parkour") && args[1].equalsIgnoreCase("setcheckpoint")) { switch (args[0].toLowerCase()) {
suggestions.addAll(Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9")); case "parkour" -> {
if (args[1].equalsIgnoreCase("setcheckpoint"))
suggestions.addAll(Arrays.asList("1","2","3","4","5","6","7","8","9"));
}
case "ball" -> {
switch (args[1].toLowerCase()) {
// /nexuslobby ball goal <?>
case "goal" -> suggestions.addAll(Arrays.asList(
"pos1", "pos2", "save", "delete", "list", "show", "debug"
));
// /nexuslobby ball score <?>
case "score" -> suggestions.add("reset");
}
}
}
// /nexuslobby <sub> <sub2> <sub3> <?>
} else if (args.length == 4) {
if (args[0].equalsIgnoreCase("ball") && args[1].equalsIgnoreCase("goal")) {
switch (args[2].toLowerCase()) {
// /nexuslobby ball goal save <Name> <?> → Tor-Name (Freitext)
case "save" -> suggestions.add("<TorName>");
// /nexuslobby ball goal delete <Name>
case "delete" -> {
// Tor-Namen aus Config laden und vorschlagen
var section = NexusLobby.getInstance().getConfig()
.getConfigurationSection("ball.goals");
if (section != null) suggestions.addAll(section.getKeys(false));
}
}
}
// /nexuslobby ball goal save <Name> <?> → Team 1 oder 2
} else if (args.length == 5) {
if (args[0].equalsIgnoreCase("ball")
&& args[1].equalsIgnoreCase("goal")
&& args[2].equalsIgnoreCase("save")) {
suggestions.addAll(Arrays.asList("1", "2"));
} }
} }
} }
// --- Hologram Befehl --- // ── Hologram Befehl ───────────────────────────────────────────────────
else if (cmdName.equals("holo")) { else if (cmdName.equals("holo")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.addAll(Arrays.asList("create", "delete")); suggestions.addAll(Arrays.asList("create", "delete"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (hologramModule != null) { if (hologramModule != null)
suggestions.addAll(hologramModule.getHologramIds()); suggestions.addAll(hologramModule.getHologramIds());
}
} }
} }
// --- Wartungsmodus --- // ── Wartungsmodus ─────────────────────────────────────────────────────
else if (cmdName.equals("maintenance")) { else if (cmdName.equals("maintenance")) {
if (args.length == 1) { if (args.length == 1)
suggestions.addAll(Arrays.asList("on", "off")); suggestions.addAll(Arrays.asList("on", "off"));
}
} }
// --- Portalsystem --- // ── Portalsystem ──────────────────────────────────────────────────────
else if (cmdName.equals("portal")) { else if (cmdName.equals("portal")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.addAll(Arrays.asList("create", "delete", "list")); suggestions.addAll(Arrays.asList("create", "delete", "list"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (portalManager != null) { if (portalManager != null)
suggestions.addAll(portalManager.getPortalNames()); suggestions.addAll(portalManager.getPortalNames());
}
} }
} }
// --- MapArt --- // ── MapArt ────────────────────────────────────────────────────────────
else if (cmdName.equals("mapart")) { else if (cmdName.equals("mapart")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("https://"); suggestions.add("https://");
@@ -95,14 +141,13 @@ public class LobbyTabCompleter implements TabCompleter {
} }
} }
// --- Intro System --- // ── Intro System ──────────────────────────────────────────────────────
else if (cmdName.equals("intro")) { else if (cmdName.equals("intro")) {
if (args.length == 1) { if (args.length == 1)
suggestions.addAll(Arrays.asList("add", "clear", "start")); suggestions.addAll(Arrays.asList("add", "clear", "start"));
}
} }
// --- WorldBorder --- // ── WorldBorder ───────────────────────────────────────────────────────
else if (cmdName.equals("border")) { else if (cmdName.equals("border")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.addAll(Arrays.asList("circle", "square", "disable")); suggestions.addAll(Arrays.asList("circle", "square", "disable"));
@@ -111,58 +156,48 @@ public class LobbyTabCompleter implements TabCompleter {
} }
} }
// --- NexusCmd / ArmorStandTools --- // ── NexusCmd / ArmorStandTools ────────────────────────────────────────
else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd") || cmdName.equals("conv")) { else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd")
|| cmdName.equals("ascmd") || cmdName.equals("conv")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say")); suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say"));
} } else if (args.length == 2) {
else if (args.length == 2) { switch (args[0].toLowerCase()) {
if (args[0].equalsIgnoreCase("add")) { case "add" -> suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9"));
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); case "name" -> suggestions.addAll(Arrays.asList("<Anzeigename>", "none"));
} else if (args[0].equalsIgnoreCase("name")) { case "conv" -> suggestions.addAll(Arrays.asList("select1","select2","select3","select4","link","unlink","start"));
suggestions.addAll(Arrays.asList("<Anzeigename>", "none")); case "remove" -> suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9","all"));
} else if (args[0].equalsIgnoreCase("conv")) { case "say" -> suggestions.add("<Nachricht>");
suggestions.addAll(Arrays.asList("select1", "select2", "select3", "select4", "link", "unlink", "start"));
} else if (args[0].equalsIgnoreCase("remove")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "all"));
} else if (args[0].equalsIgnoreCase("say")) {
suggestions.add("<Nachricht>");
} }
} } else if (args.length == 3) {
else if (args.length == 3) {
if (args[0].equalsIgnoreCase("add")) { if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9"));
} else if (args[0].equalsIgnoreCase("conv")) { } else if (args[0].equalsIgnoreCase("conv")
if (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link")) { && (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link"))) {
if (NexusLobby.getInstance().getConversationManager() != null) { if (NexusLobby.getInstance().getConversationManager() != null)
suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds()); suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds());
}
}
} }
} } else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("bungee", "console", "player")); suggestions.addAll(Arrays.asList("bungee", "console", "player"));
} } else if (args.length == 5 && args[0].equalsIgnoreCase("add")
else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) { && args[3].equalsIgnoreCase("bungee")) {
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) { var section = NexusLobby.getInstance().getConfig().getConfigurationSection("servers");
suggestions.addAll(NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false)); if (section != null) suggestions.addAll(section.getKeys(false));
}
} }
} }
// --- ArmorStandTools Alternate --- // ── ArmorStandTools Alternate ─────────────────────────────────────────
else if (cmdName.equals("astools") || cmdName.equals("nt") || cmdName.equals("ntools")) { else if (cmdName.equals("astools") || cmdName.equals("nt") || cmdName.equals("ntools")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.addAll(Arrays.asList("dynamic", "lookat", "addplayer", "addconsole", "remove", "reload", "say")); suggestions.addAll(Arrays.asList("dynamic","lookat","addplayer","addconsole","remove","reload","say"));
} } else if (args.length == 2) {
else if (args.length == 2 && args[0].equalsIgnoreCase("say")) { if (args[0].equalsIgnoreCase("say"))
suggestions.add("<Nachricht>"); suggestions.add("<Nachricht>");
} else if (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))
else if (args.length == 2 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) { suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9"));
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); } else if (args.length == 3) {
} if (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))
else if (args.length == 3 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) { suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9"));
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} }
} }

View File

@@ -24,7 +24,7 @@ public class NexusLobbyCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
sender.sendMessage("§cDieser Befehl ist nur für Spieler!"); sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player"));
return true; return true;
} }
@@ -40,12 +40,13 @@ public class NexusLobbyCommand implements CommandExecutor {
if (cmdName.equalsIgnoreCase("setcheckpoint")) { if (cmdName.equalsIgnoreCase("setcheckpoint")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setCheckpoint(player, player.getLocation()); pm.setCheckpoint(player, player.getLocation());
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_checkpoint_set"));
return true; return true;
} }
if (cmdName.equalsIgnoreCase("setfinish")) { if (cmdName.equalsIgnoreCase("setfinish")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setFinishLocation(player.getLocation()); pm.setFinishLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set"));
return true; return true;
} }
@@ -57,12 +58,12 @@ public class NexusLobbyCommand implements CommandExecutor {
if (loc != null) { if (loc != null) {
player.teleport(loc); player.teleport(loc);
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.2f); player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.2f);
player.sendMessage("§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert."); player.sendMessage(de.nexuslobby.utils.LangManager.get("teleport_spawn"));
} else { } else {
player.sendMessage("§cFehler: Die Spawn-Welt existiert nicht."); player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_world_missing"));
} }
} else { } else {
player.sendMessage("§cEs wurde noch kein Spawn gesetzt."); player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_not_set"));
} }
return true; return true;
} }
@@ -77,10 +78,16 @@ public class NexusLobbyCommand implements CommandExecutor {
case "reload": case "reload":
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
NexusLobby.getInstance().reloadPlugin(); NexusLobby.getInstance().reloadPlugin();
player.sendMessage("§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("plugin_reloaded"));
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f); player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f);
break; break;
case "cleanbubbles":
// Bereinigt alle hängengebliebenen Sprechblasen-ArmorStands im gesamten Server.
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
handleCleanBubbles(player);
break;
case "setspawn": case "setspawn":
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
Location loc = player.getLocation(); Location loc = player.getLocation();
@@ -92,17 +99,17 @@ public class NexusLobbyCommand implements CommandExecutor {
config.set("spawn.yaw", (double) loc.getYaw()); config.set("spawn.yaw", (double) loc.getYaw());
config.set("spawn.pitch", (double) loc.getPitch()); config.set("spawn.pitch", (double) loc.getPitch());
NexusLobby.getInstance().saveConfig(); NexusLobby.getInstance().saveConfig();
player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_set"));
break; break;
case "silentjoin": case "silentjoin":
if (!player.hasPermission("nexuslobby.silentjoin")) return noPerm(player); if (!player.hasPermission("nexuslobby.silentjoin")) return noPerm(player);
if (NexusLobby.getInstance().getSilentPlayers().contains(player.getUniqueId())) { if (NexusLobby.getInstance().getSilentPlayers().contains(player.getUniqueId())) {
NexusLobby.getInstance().getSilentPlayers().remove(player.getUniqueId()); NexusLobby.getInstance().getSilentPlayers().remove(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §7Silent Join: §cDeaktiviert"); player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_off"));
} else { } else {
NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId()); NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §7Silent Join: §aAktiviert"); player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_on"));
} }
break; break;
@@ -114,12 +121,12 @@ public class NexusLobbyCommand implements CommandExecutor {
if (NexusLobby.getInstance().getSoccerModule() != null) { if (NexusLobby.getInstance().getSoccerModule() != null) {
return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args); return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args);
} }
player.sendMessage("§cDas Fußball-Modul ist nicht geladen."); player.sendMessage(de.nexuslobby.utils.LangManager.get("soccer_module_not_loaded"));
break; break;
case "parkour": case "parkour":
if (args.length < 2) { if (args.length < 2) {
player.sendMessage("§8[§6Nexus§8] §7Nutze: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage"));
return true; return true;
} }
@@ -132,26 +139,26 @@ public class NexusLobbyCommand implements CommandExecutor {
break; break;
case "setfinish": case "setfinish":
pm.setFinishLocation(player.getLocation()); pm.setFinishLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set"));
break; break;
case "setcheckpoint": case "setcheckpoint":
pm.setCheckpoint(player, player.getLocation()); pm.setCheckpoint(player, player.getLocation());
break; break;
case "reset": case "reset":
pm.stopParkour(player); pm.stopParkour(player);
player.sendMessage("§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen."); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_run_aborted"));
break; break;
case "clear": case "clear":
pm.clearStats(); pm.clearStats();
player.sendMessage("§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_besttimes_cleared"));
break; break;
case "removeall": case "removeall":
pm.removeAllPoints(); pm.removeAllPoints();
player.sendMessage("§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_track_removed"));
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f);
break; break;
default: default:
player.sendMessage("§cUnbekannter Unterbefehl."); player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand"));
break; break;
} }
break; break;
@@ -180,26 +187,37 @@ public class NexusLobbyCommand implements CommandExecutor {
if (targetAs != null) { if (targetAs != null) {
targetAs.addScoreboardTag("parkour_npc"); targetAs.addScoreboardTag("parkour_npc");
player.sendMessage("§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_npc_marked"));
} }
pm.setStartLocation(player.getLocation()); pm.setStartLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_start_set"));
}
private void handleCleanBubbles(Player player) {
de.nexuslobby.modules.armorstandtools.ConversationManager cm =
NexusLobby.getInstance().getConversationManager();
if (cm == null) {
player.sendMessage("§8[§6Nexus§8] §cConversationManager ist nicht aktiv.");
return;
}
cm.clearHangingBubbles();
player.sendMessage("§8[§6Nexus§8] §aAlle hängengebliebenen Sprechblasen wurden entfernt.");
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
} }
private boolean noPerm(Player player) { private boolean noPerm(Player player) {
player.sendMessage("§cKeine Berechtigung."); player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
return true; return true;
} }
private void handleScoreboard(Player player, String[] args) { private void handleScoreboard(Player player, String[] args) {
if (args.length < 2) { if (args.length < 2) {
player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>"); player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_usage"));
return; return;
} }
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class); ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) { if (sbModule == null) {
player.sendMessage("§cScoreboard-Modul ist deaktiviert."); player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_module_disabled"));
return; return;
} }
String sub = args[1].toLowerCase(); String sub = args[1].toLowerCase();
@@ -208,11 +226,11 @@ public class NexusLobbyCommand implements CommandExecutor {
case "off": sbModule.setVisibility(player, false); break; case "off": sbModule.setVisibility(player, false); break;
case "admin": case "admin":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true); if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true);
else player.sendMessage("§cKeine Rechte."); else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
break; break;
case "spieler": case "spieler":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false); if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false);
else player.sendMessage("§cKeine Rechte."); else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
break; break;
} }
} }
@@ -226,16 +244,17 @@ public class NexusLobbyCommand implements CommandExecutor {
} }
private void sendInfo(Player player) { private void sendInfo(Player player) {
player.sendMessage("§8§m--------------------------------------"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_header"));
player.sendMessage("§6§lNexusLobby §7- Informationen"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_title"));
player.sendMessage(""); player.sendMessage("");
player.sendMessage("§f/spawn §7- Zum Spawn"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_spawn"));
player.sendMessage("§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_parkour"));
player.sendMessage("§f/nexus parkour removeall §7- Strecke löschen"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_removeall"));
player.sendMessage("§f/nexus ball setspawn §7- Fußball Spawn setzen"); // NEU player.sendMessage(de.nexuslobby.utils.LangManager.get("info_ball"));
player.sendMessage("§f/nexus setspawn §7- Spawn setzen"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_setspawn"));
player.sendMessage("§f/nexus sb <on|off> §7- Scoreboard"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_scoreboard"));
player.sendMessage("§f/nexus reload §7- Config laden"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_reload"));
player.sendMessage("§8§m--------------------------------------"); player.sendMessage("§e/nexuslobby cleanbubbles §7- Hängende Sprechblasen entfernen");
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_footer"));
} }
} }

View File

@@ -56,8 +56,11 @@ public class ItemsModule implements Module, Listener {
// 2. Gadget GUI Logik // 2. Gadget GUI Logik
String gadgetName = colorize(config.getString("items.lobby-tools.gadget.displayname", "&bGadgets")); String gadgetName = colorize(config.getString("items.lobby-tools.gadget.displayname", "&bGadgets"));
if (displayName.equals(gadgetName)) { if (displayName.equals(gadgetName)) {
// Öffnet die GUI aus dem GadgetModule // Öffnet die GUI aus dem GadgetModule (falls vorhanden)
NexusLobby.getInstance().getGadgetModule().openGUI(player); var gadgetModule = NexusLobby.getInstance().getGadgetModule();
if (gadgetModule != null) {
gadgetModule.openGUI(player);
}
event.setCancelled(true); event.setCancelled(true);
return; return;
} }

View File

@@ -6,11 +6,14 @@ import de.nexuslobby.commands.BuildCommand;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
@@ -30,7 +33,11 @@ public class ProtectionModule implements Module, Listener {
} }
@Override @Override
public void onDisable() {} public void onDisable() {
// FIX: Listener nach dem Deaktivieren abmelden, damit nach /reload
// keine doppelten Events ausgelöst werden.
HandlerList.unregisterAll(this);
}
@EventHandler @EventHandler
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
@@ -91,4 +98,22 @@ public class ProtectionModule implements Module, Listener {
} }
} }
} }
// FIX: Fehlender Explosionsschutz - settings.yml hat allowExplosions: false,
// aber bisher gab es keinen Handler dafür.
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (!getSettings().getBoolean("allowExplosions", false)) {
event.blockList().clear();
event.setCancelled(true);
}
}
@EventHandler
public void onBlockExplode(BlockExplodeEvent event) {
if (!getSettings().getBoolean("allowExplosions", false)) {
event.blockList().clear();
event.setCancelled(true);
}
}
} }

View File

@@ -15,9 +15,14 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class ScoreboardModule implements Module { import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class ScoreboardModule implements Module, Listener {
private final NexusLobby plugin = NexusLobby.getInstance(); private final NexusLobby plugin = NexusLobby.getInstance();
private boolean placeholderAPIEnabled;
// Speicher für die aktuellen Spieler-Einstellungen (bis zum Restart/Reload) // Speicher für die aktuellen Spieler-Einstellungen (bis zum Restart/Reload)
private final Set<UUID> hiddenPlayers = new HashSet<>(); private final Set<UUID> hiddenPlayers = new HashSet<>();
@@ -33,6 +38,11 @@ public class ScoreboardModule implements Module {
FileConfiguration vConfig = plugin.getVisualsConfig(); FileConfiguration vConfig = plugin.getVisualsConfig();
if (!vConfig.getBoolean("scoreboard.enabled", true)) return; if (!vConfig.getBoolean("scoreboard.enabled", true)) return;
// Listener registrieren
Bukkit.getPluginManager().registerEvents(this, plugin);
placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null;
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
@@ -43,6 +53,21 @@ public class ScoreboardModule implements Module {
}.runTaskTimer(plugin, 0L, vConfig.getLong("scoreboard.update_ticks", 20L)); }.runTaskTimer(plugin, 0L, vConfig.getLong("scoreboard.update_ticks", 20L));
} }
/**
* Setzt Scoreboard-Status beim Join gemäß config.yml (scoreboard-default-visible)
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
boolean defaultVisible = plugin.getConfig().getBoolean("scoreboard-default-visible", true);
if (!defaultVisible) {
hiddenPlayers.add(player.getUniqueId());
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
} else {
hiddenPlayers.remove(player.getUniqueId());
}
}
private void updateSidebar(Player player) { private void updateSidebar(Player player) {
// 1. Prüfen, ob der Spieler das Scoreboard ausgeblendet hat // 1. Prüfen, ob der Spieler das Scoreboard ausgeblendet hat
if (hiddenPlayers.contains(player.getUniqueId())) { if (hiddenPlayers.contains(player.getUniqueId())) {
@@ -124,12 +149,20 @@ public class ScoreboardModule implements Module {
} }
private String translate(Player player, String text) { private String translate(Player player, String text) {
String translated = PlaceholderAPI.setPlaceholders(player, text); String translated = text;
if (placeholderAPIEnabled) {
try {
translated = PlaceholderAPI.setPlaceholders(player, text);
} catch (NoClassDefFoundError ignored) {
// PlaceholderAPI fehlt zur Laufzeit
}
}
return ChatColor.translateAlternateColorCodes('&', translated); return ChatColor.translateAlternateColorCodes('&', translated);
} }
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
} }

View File

@@ -70,32 +70,34 @@ public class ASTListener implements Listener {
/** /**
* Prüft, ob der ArmorStand Dialog-Tags hat und startet das Gespräch über den Manager. * Prüft, ob der ArmorStand Dialog-Tags hat und startet das Gespräch über den Manager.
* Nutzt nun die Gruppen-Logik (z.B. owner_suche). * FIX: Liest jetzt alle Partner-Tags (conv_partner, conv_partner2, conv_partner3)
* und verwendet playConversationGroup statt nur die 2-NPC-Methode.
* Vorher wurden Gruppen-Gespräche mit 3-4 NPCs als 2-NPC-Gespräch ausgeführt.
*/ */
private void checkAndTriggerDialog(ArmorStand as, Player p) { private void checkAndTriggerDialog(ArmorStand as, Player p) {
String groupName = null; String groupName = null;
String partnerUUIDString = null; String partner1 = null, partner2 = null, partner3 = null;
for (String tag : as.getScoreboardTags()) { for (String tag : as.getScoreboardTags()) {
// conv_id enthält jetzt den Gruppennamen aus der conversations.yml if (tag.startsWith("conv_id:")) groupName = tag.split(":", 2)[1];
if (tag.startsWith("conv_id:")) groupName = tag.split(":")[1]; if (tag.startsWith("conv_partner:")) partner1 = tag.split(":", 2)[1];
if (tag.startsWith("conv_partner:")) partnerUUIDString = tag.split(":")[1]; if (tag.startsWith("conv_partner2:")) partner2 = tag.split(":", 2)[1];
if (tag.startsWith("conv_partner3:")) partner3 = tag.split(":", 2)[1];
} }
if (groupName != null && partnerUUIDString != null) { if (groupName == null || partner1 == null) return;
try {
UUID partnerUUID = UUID.fromString(partnerUUIDString);
// Wir rufen playConversation auf. Der Manager entscheidet selbst try {
// anhand der Uhrzeit, ob er morgens, mittags oder nachts abspielt. java.util.List<UUID> participants = new java.util.ArrayList<>();
NexusLobby.getInstance().getConversationManager().playConversation( participants.add(as.getUniqueId());
as.getUniqueId(), participants.add(UUID.fromString(partner1));
partnerUUID, if (partner2 != null) participants.add(UUID.fromString(partner2));
groupName if (partner3 != null) participants.add(UUID.fromString(partner3));
);
} catch (Exception ignored) { NexusLobby.getInstance().getConversationManager()
// Falls die UUID oder Gruppe ungültig ist .playConversationGroup(participants, groupName);
} } catch (Exception ignored) {
// Ungültige UUID oder fehlende Gruppe still ignorieren
} }
} }

View File

@@ -34,7 +34,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
if (!(sender instanceof Player p)) return true; if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.armorstand.cmd")) { if (!p.hasPermission("nexuslobby.armorstand.cmd")) {
p.sendMessage(prefix + "§cKeine Berechtigung!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("no_permission"));
return true; return true;
} }
@@ -53,7 +53,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
case "select4": case "select4":
ArmorStand target = getTargetArmorStand(p); ArmorStand target = getTargetArmorStand(p);
if (target == null) { if (target == null) {
p.sendMessage(prefix + "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required"));
return true; return true;
} }
@@ -69,11 +69,11 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
case "link": case "link":
if (args.length < 3) { if (args.length < 3) {
p.sendMessage(prefix + "§cNutze: /nexuscmd conv link <Dialog-ID>"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_usage"));
return true; return true;
} }
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_npcs"));
return true; return true;
} }
@@ -101,8 +101,8 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
// Im Manager speichern (Nutzt die erweiterte Methode für Gruppen) // Im Manager speichern (Nutzt die erweiterte Methode für Gruppen)
NexusLobby.getInstance().getConversationManager().saveLinkExtended(id1, id2, id3, id4, dialogId); NexusLobby.getInstance().getConversationManager().saveLinkExtended(id1, id2, id3, id4, dialogId);
p.sendMessage(prefix + "§a§lDauerhafte Verknüpfung erstellt!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_created"));
p.sendMessage(prefix + "§7Beteiligte NPCs: §e" + (id3 == null ? "2" : (id4 == null ? "3" : "4"))); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_npcs").replace("{count}", (id3 == null ? "2" : (id4 == null ? "3" : "4"))));
p.spawnParticle(Particle.HAPPY_VILLAGER, as1.getLocation().add(0, 1.5, 0), 20, 0.4, 0.4, 0.4, 0.1); p.spawnParticle(Particle.HAPPY_VILLAGER, as1.getLocation().add(0, 1.5, 0), 20, 0.4, 0.4, 0.4, 0.1);
// Metadaten nach dem Linken aufräumen // Metadaten nach dem Linken aufräumen
@@ -111,14 +111,14 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
p.removeMetadata("conv_npc3", NexusLobby.getInstance()); p.removeMetadata("conv_npc3", NexusLobby.getInstance());
p.removeMetadata("conv_npc4", NexusLobby.getInstance()); p.removeMetadata("conv_npc4", NexusLobby.getInstance());
} else { } else {
p.sendMessage(prefix + "§cFehler: Sprecher 1 nicht gefunden."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_speaker_not_found"));
} }
break; break;
case "unlink": case "unlink":
ArmorStand targetUnlink = getTargetArmorStand(p); ArmorStand targetUnlink = getTargetArmorStand(p);
if (targetUnlink == null) { if (targetUnlink == null) {
p.sendMessage(prefix + "§cSchau den NPC an, dessen Verknüpfung du lösen willst!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_unlink_lookat"));
return true; return true;
} }
@@ -128,17 +128,17 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
// Aus Konfiguration löschen // Aus Konfiguration löschen
NexusLobby.getInstance().getConversationManager().removeLink(targetUnlink.getUniqueId()); NexusLobby.getInstance().getConversationManager().removeLink(targetUnlink.getUniqueId());
p.sendMessage(prefix + "§eNPC-Verknüpfung wurde aufgehoben."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_unlinked"));
p.spawnParticle(Particle.SMOKE, targetUnlink.getLocation().add(0, 1.0, 0), 20, 0.2, 0.2, 0.2, 0.02); p.spawnParticle(Particle.SMOKE, targetUnlink.getLocation().add(0, 1.0, 0), 20, 0.2, 0.2, 0.2, 0.02);
break; break;
case "start": case "start":
if (args.length < 3) { if (args.length < 3) {
p.sendMessage(prefix + "§cNutze: /nexuscmd conv start <ID>"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_start_usage"));
return true; return true;
} }
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + "§cBitte markiere mindestens zwei NPCs!"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_two_npcs"));
return true; return true;
} }
@@ -163,7 +163,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
ArmorStand target = getTargetArmorStand(p); ArmorStand target = getTargetArmorStand(p);
if (args[0].equalsIgnoreCase("say") && args.length >= 2) { if (args[0].equalsIgnoreCase("say") && args.length >= 2) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
String text = buildString(args, 1); String text = buildString(args, 1);
String colored = ChatColor.translateAlternateColorCodes('&', text); String colored = ChatColor.translateAlternateColorCodes('&', text);
@@ -171,25 +171,25 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
// Nutzt die showBubble-Logik aus dem ConversationManager (Ohne Partner-Zwang) // Nutzt die showBubble-Logik aus dem ConversationManager (Ohne Partner-Zwang)
NexusLobby.getInstance().getConversationManager().showBubble(target, colored); NexusLobby.getInstance().getConversationManager().showBubble(target, colored);
p.sendMessage(prefix + "§aNPC-Sprechblase gesendet."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("npc_bubble_sent"));
return true; return true;
} }
if (args[0].equalsIgnoreCase("lookat")) { if (args[0].equalsIgnoreCase("lookat")) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
if (target.getScoreboardTags().contains("as_lookat")) { if (target.getScoreboardTags().contains("as_lookat")) {
target.removeScoreboardTag("as_lookat"); target.removeScoreboardTag("as_lookat");
target.setHeadPose(new EulerAngle(0, 0, 0)); target.setHeadPose(new EulerAngle(0, 0, 0));
p.sendMessage(prefix + "§cBlickkontakt aus."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_off"));
} else { } else {
target.addScoreboardTag("as_lookat"); target.addScoreboardTag("as_lookat");
p.sendMessage(prefix + "§aBlickkontakt an."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_on"));
} }
return true; return true;
} }
if (args[0].equalsIgnoreCase("name") && args.length >= 2) { if (args[0].equalsIgnoreCase("name") && args.length >= 2) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
String nameInput = buildString(args, 1); String nameInput = buildString(args, 1);
// Wichtig: Alle alten Namens-Tags entfernen für Konsistenz // Wichtig: Alle alten Namens-Tags entfernen für Konsistenz
@@ -198,7 +198,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
if (nameInput.equalsIgnoreCase("none")) { if (nameInput.equalsIgnoreCase("none")) {
target.setCustomName(""); target.setCustomName("");
target.setCustomNameVisible(false); target.setCustomNameVisible(false);
p.sendMessage(prefix + "§eName entfernt."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_removed"));
} else { } else {
String colored = ChatColor.translateAlternateColorCodes('&', nameInput); String colored = ChatColor.translateAlternateColorCodes('&', nameInput);
target.setCustomName(colored); target.setCustomName(colored);
@@ -208,33 +208,33 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
// ":" wird durch "§§" ersetzt, um Probleme in Scoreboard-Tags zu vermeiden // ":" wird durch "§§" ersetzt, um Probleme in Scoreboard-Tags zu vermeiden
target.addScoreboardTag("as_displayname:" + nameInput.replace(":", "§§")); target.addScoreboardTag("as_displayname:" + nameInput.replace(":", "§§"));
p.sendMessage(prefix + "§7Name gesetzt: " + colored); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_set").replace("{name}", colored));
p.sendMessage(prefix + "§8(Status-Backup wurde gespeichert)"); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("status_backup_saved"));
} }
return true; return true;
} }
if (args[0].equalsIgnoreCase("add") && args.length >= 5) { if (args[0].equalsIgnoreCase("add") && args.length >= 5) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
String slot1 = args[1], slot2 = args[2], type = args[3].toLowerCase(); String slot1 = args[1], slot2 = args[2], type = args[3].toLowerCase();
String cmdStr = buildString(args, 4); String cmdStr = buildString(args, 4);
target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr); target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr);
p.sendMessage(prefix + "§aBefehl gebunden."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("command_bound"));
return true; return true;
} }
if (args[0].equalsIgnoreCase("list")) { if (args[0].equalsIgnoreCase("list")) {
if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; }
p.sendMessage("§6§lBefehle & Tags:"); p.sendMessage(de.nexuslobby.utils.LangManager.get("commands_and_tags"));
target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t)); target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t));
return true; return true;
} }
if (args[0].equalsIgnoreCase("remove")) { if (args[0].equalsIgnoreCase("remove")) {
if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; }
target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:")); target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
p.sendMessage(prefix + "§eAlle Befehle gelöscht."); p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("commands_removed"));
return true; return true;
} }

View File

@@ -49,6 +49,7 @@ public class ArmorStandPoseGUI {
private static ItemStack createPartIcon(Material mat, String name, EulerAngle angle) { private static ItemStack createPartIcon(Material mat, String name, EulerAngle angle) {
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name); meta.setDisplayName(name);
meta.setLore(Arrays.asList( meta.setLore(Arrays.asList(
"§8Rotation:", "§8Rotation:",
@@ -64,6 +65,7 @@ public class ArmorStandPoseGUI {
private static ItemStack createAxisItem(Material mat, String name, double angleRad) { private static ItemStack createAxisItem(Material mat, String name, double angleRad) {
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name); meta.setDisplayName(name);
meta.setLore(Arrays.asList( meta.setLore(Arrays.asList(
"§7Aktueller Winkel: §f" + Math.round(Math.toDegrees(angleRad)) + "°", "§7Aktueller Winkel: §f" + Math.round(Math.toDegrees(angleRad)) + "°",
@@ -78,6 +80,7 @@ public class ArmorStandPoseGUI {
private static ItemStack createNamedItem(Material mat, String name) { private static ItemStack createNamedItem(Material mat, String name) {
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name); meta.setDisplayName(name);
item.setItemMeta(meta); item.setItemMeta(meta);
return item; return item;

View File

@@ -99,8 +99,11 @@ public enum ArmorStandTool {
} }
public static ArmorStandTool get(ItemStack item) { public static ArmorStandTool get(ItemStack item) {
if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return null; if (item == null || !item.hasItemMeta()) return null;
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace(" ", "_"); ItemMeta meta = item.getItemMeta();
// FIX: getItemMeta() kann null zurückgeben; hasDisplayName() vor getDisplayName() prüfen
if (meta == null || !meta.hasDisplayName()) return null;
String name = ChatColor.stripColor(meta.getDisplayName()).replace(" ", "_");
if (name.equals("Pose_Editor_öffnen")) return OPEN_POSE_EDITOR; if (name.equals("Pose_Editor_öffnen")) return OPEN_POSE_EDITOR;
if (name.equals("Gesprächs-Setup")) return CONV_SETUP; if (name.equals("Gesprächs-Setup")) return CONV_SETUP;
try { return valueOf(name); } catch (Exception e) { return null; } try { return valueOf(name); } catch (Exception e) { return null; }

View File

@@ -45,13 +45,15 @@ public class ConversationManager {
/** /**
* Entfernt alle verwaisten Sprechblasen in allen Welten. * Entfernt alle verwaisten Sprechblasen in allen Welten.
* FIX: Nur ArmorStands mit dem Tag "nexus_bubble" werden entfernt.
* Die alte Bedingung (!isVisible && isMarker && customName != null) war
* zu breit und hätte auch legitime Marker-ArmorStands anderer Systeme gelöscht.
*/ */
public void clearHangingBubbles() { public void clearHangingBubbles() {
int count = 0; int count = 0;
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
if (as.getScoreboardTags().contains("nexus_bubble") || if (as.getScoreboardTags().contains("nexus_bubble")) {
(!as.isVisible() && as.isMarker() && as.getCustomName() != null)) {
as.remove(); as.remove();
count++; count++;
} }
@@ -265,7 +267,11 @@ public class ConversationManager {
} }
private void saveConfig() { private void saveConfig() {
try { config.save(file); } catch (IOException e) { e.printStackTrace(); } try {
config.save(file);
} catch (IOException e) {
Bukkit.getLogger().severe("Fehler beim Speichern der Conversations: " + e.getMessage());
}
} }
public void reload() { public void reload() {

View File

@@ -27,7 +27,6 @@ public class DynamicArmorStandModule implements Module {
@Override @Override
public void onEnable() { public void onEnable() {
startUpdateTask(); startUpdateTask();
Bukkit.getLogger().info("§a[NexusLobby] DynamicArmorStandModule aktiv: Ingame-Zeit & Sternen-Effekt geladen.");
} }
private void startUpdateTask() { private void startUpdateTask() {

View File

@@ -7,6 +7,7 @@ import org.bukkit.block.Block;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@@ -19,294 +20,723 @@ import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile; import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.UUID; import java.util.*;
import java.util.Objects;
public class SoccerModule implements Module, Listener, CommandExecutor { public class SoccerModule implements Module, Listener, CommandExecutor {
private ArmorStand ball; // =========================================================================
private Location spawnLocation; // KONSTANTEN
private long lastMoveTime; // =========================================================================
private final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e"; private static final String TEXTURE_URL =
private final String BALL_TAG = "nexusball_entity"; // Eindeutiges Tag zur Identifizierung "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
private final String BALL_NAME = "§x§N§e§x§u§s§B§a§l§l"; // Zusätzliche Erkennung private static final String BALL_TAG = "nexusball_entity";
private static final String BALL_NAME = "NexusBall";
@Override private static final double DRIBBLE_DETECTION_RADIUS = 0.7;
public String getName() { return "Soccer"; } private static final double DRIBBLE_HEIGHT = 0.5;
private static final double DRIBBLE_FORCE = 0.35;
private static final double DRIBBLE_LIFT = 0.12;
private static final double KICK_FORCE = 1.35;
private static final double KICK_LIFT = 0.38;
private static final double WALL_BOUNCE_DAMPING = 0.75;
private static final double WALL_CHECK_DISTANCE = 1.3;
private static final double VOID_THRESHOLD = -5.0;
private static final double CLEANUP_RADIUS = 5.0;
private static final double PARTICLE_SPEED_HIGH = 0.85;
private static final double PARTICLE_SPEED_MEDIUM = 0.45;
private static final double PARTICLE_SPEED_MIN = 0.05;
// Tor-Erkennung: Schrittweite beim Segment-Check in Blöcken.
// 0.1 → bei 1.35 b/t Kick-Speed ~14 Prüfpunkte pro Tick. Sehr sicher.
private static final double CHECK_STEP = 0.1;
// ── Tor-Partikel ──────────────────────────────────────────────────────────
private static final Particle.DustOptions DUST_TEAM1 = new Particle.DustOptions(Color.fromRGB(30, 120, 255), 1.2f);
private static final Particle.DustOptions DUST_TEAM2 = new Particle.DustOptions(Color.fromRGB(255, 50, 50), 1.2f);
private static final Particle.DustOptions DUST_GOAL_FLASH = new Particle.DustOptions(Color.YELLOW, 2.0f);
private static final double PARTICLE_STEP = 0.35;
private static final long GOAL_RESPAWN_DELAY = 80L;
private static final long GOAL_COOLDOWN_MS = 3_000L;
// =========================================================================
// LAUFZEIT-ZUSTAND
// =========================================================================
private ArmorStand ball;
private Location spawnLocation;
private long lastMoveTime;
private long lastGoalTime = 0L;
// ── Tor-Erkennung: Position aus dem VORHERIGEN Tick ───────────────────────
//
// WARUM DAS FUNKTIONIERT:
// getLocation() eines ArmorStands liefert in Tick N die Position aus Tick N-1.
// Das klingt wie ein Problem, ist aber tatsächlich die Lösung:
//
// Wenn wir in jedem Tick:
// 1. prevPos = die aktuelle getLocation() merken (= echte Position aus letztem Tick)
// 2. Am ENDE des Ticks: currPos = getLocation() lesen
// 3. Das Segment prevPos→currPos prüfen
//
// Dann deckt dieses Segment exakt den Weg ab den der Ball in Minecraft
// in den letzten 50ms zurückgelegt hat egal wie schnell, egal ob Kick
// oder Dribble oder geschoben.
//
// WICHTIG: prevPos wird am ANFANG des Ticks gesetzt (bevor Physik-Berechnungen
// die Velocity ändern), currPos am ENDE. So ist das Segment vollständig.
private Location prevPos = null;
private final Map<String, SoccerGoal> goals = new LinkedHashMap<>();
private final Map<Integer, Integer> scores = new HashMap<>();
private final Map<UUID, Location> selPos1 = new HashMap<>();
private final Map<UUID, Location> selPos2 = new HashMap<>();
// =========================================================================
// MODULE-LIFECYCLE
// =========================================================================
@Override public String getName() { return "Soccer"; }
@Override @Override
public void onEnable() { public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("nexuslobby") != null) {
Objects.requireNonNull(NexusLobby.getInstance().getCommand("nexuslobby")).setExecutor(this);
}
loadConfigLocation(); loadConfigLocation();
loadGoals();
scores.put(1, 0);
scores.put(2, 0);
// AGGRESSIVES MEHRFACHES CLEANUP-SYSTEM removeAllOldBalls();
// 1. Sofort beim Enable
removeAllOldBallsGlobal();
// 2. Nach 0.5 Sekunden
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 10L);
// 3. Nach 1 Sekunde
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 20L);
// 4. Nach 2 Sekunden - cleanup und dann spawnen
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
removeAllOldBallsGlobal(); removeAllOldBalls();
spawnBall(); spawnBall();
}, 40L); }, 40L);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBalls, 100L);
// 5. Nach 3 Sekunden - finales Cleanup für hartnäckige Duplikate // ── Physik-Tick ────────────────────────────────────────────────────────
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 60L);
// 6. Nach 5 Sekunden - letzter Check
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 100L);
// Haupt-Physik & Anti-Klon Tick
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
if (ball != null && ball.isValid()) removeDuplicateBalls();
// ANTI-KLON-SYSTEM: Prüfe die Umgebung des Spawns auf illegale Kopien
if (spawnLocation != null && spawnLocation.getWorld() != null) {
for (Entity entity : spawnLocation.getWorld().getNearbyEntities(spawnLocation, 50, 50, 50)) {
if (entity instanceof ArmorStand stand) {
// Wenn der ArmorStand unser Tag hat, aber nicht unsere aktive Instanz ist -> Löschen
if (stand.getScoreboardTags().contains(BALL_TAG)) {
if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) {
stand.remove();
}
}
}
}
}
if (ball == null || !ball.isValid()) return; if (ball == null || !ball.isValid()) return;
Vector vel = ball.getVelocity(); // ── SCHRITT 1: Aktuelle Position als "vorherige" speichern ─────────
double speed = vel.length(); // getLocation() ist hier die Position aus dem letzten Server-Tick
// d.h. die Position BEVOR die Physik dieses Ticks berechnet wurde.
Location currentPos = ball.getLocation().clone();
// ── SCHRITT 2: Physik und Spieler-Interaktion verarbeiten ──────────
Vector vel = ball.getVelocity();
double speed = vel.length();
handleWallBounce(vel); handleWallBounce(vel);
handleParticles(speed); handleParticles(speed);
handleDribbling();
// Dribbel-Logik // ── SCHRITT 3: Nach Physik die neue Position lesen ─────────────────
for (Entity nearby : ball.getNearbyEntities(0.7, 0.5, 0.7)) { // Minecraft hat jetzt die Position um vel verschoben.
if (nearby instanceof Player p) { // Da getLocation() einen Tick verzögert ist, lesen wir hier noch
Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector()); // currentPos aber über den Velocity-Vektor wissen wir die neue Pos.
if (direction.lengthSquared() > 0) { // Wir nutzen deshalb: newPos = currentPos + velocity (vor Drag)
direction.normalize(); // Das ist die tatsächliche neue Ballposition nach diesem Tick.
direction.setY(0.12); Location newPos = currentPos.clone().add(vel);
ball.setVelocity(direction.multiply(0.35));
} // ── SCHRITT 4: Segment prüfen ─────────────────────────────────────
lastMoveTime = System.currentTimeMillis(); // Das Segment prevPos → newPos deckt den vollständigen Weg ab den
} // der Ball seit dem letzten Tor-Check zurückgelegt hat.
if (!goals.isEmpty()
&& System.currentTimeMillis() - lastGoalTime >= GOAL_COOLDOWN_MS
&& prevPos != null) {
checkSegment(prevPos, newPos, currentPos.getWorld());
} }
// Automatischer Respawn bei Inaktivität oder Void // ── SCHRITT 5: prevPos für nächsten Tick aktualisieren ─────────────
long delay = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000; prevPos = newPos.clone();
if (System.currentTimeMillis() - lastMoveTime > delay || ball.getLocation().getY() < -5) {
// ── Respawn-Check ──────────────────────────────────────────────────
long respawnDelayMs = NexusLobby.getInstance().getConfig()
.getLong("ball.respawn_delay", 60) * 1_000L;
if (System.currentTimeMillis() - lastMoveTime > respawnDelayMs
|| ball.getLocation().getY() < VOID_THRESHOLD) {
respawnBall(); respawnBall();
} }
}, 1L, 1L); }, 1L, 1L);
// ── Tor-Partikel ──────────────────────────────────────────────────────
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(),
this::drawAllGoalParticles, 4L, 4L);
}
@Override
public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
if (ball != null && ball.isValid()) { ball.remove(); ball = null; }
}
// =========================================================================
// TOR-ERKENNUNG
// =========================================================================
/**
* Prüft das Segment von {@code from} nach {@code to} auf Tor-Treffer.
*
* Das Segment wird in CHECK_STEP (0.1 Block) Schritte unterteilt.
* An jedem Punkt werden drei Y-Höhen geprüft (Fuß / Mitte / Kopf des Balls).
*
* @param from Position am Anfang des Ticks
* @param to Position am Ende des Ticks (= from + velocity)
* @param world Welt des Balls
*/
private void checkSegment(Location from, Location to, World world) {
if (world == null) return;
double dx = to.getX() - from.getX();
double dy = to.getY() - from.getY();
double dz = to.getZ() - from.getZ();
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
// Ball steht fast still → nur aktuellen Punkt prüfen
if (len < 0.001) {
checkPoint(from.getX(), from.getY(), from.getZ(), world);
return;
}
int steps = Math.max(1, (int) Math.ceil(len / CHECK_STEP));
double sx = dx / steps;
double sy = dy / steps;
double sz = dz / steps;
for (int i = 0; i <= steps; i++) {
double x = from.getX() + sx * i;
double y = from.getY() + sy * i;
double z = from.getZ() + sz * i;
if (checkPoint(x, y, z, world)) return;
}
} }
/** /**
* Scannt ALLE Welten nach Entities mit dem BALL_TAG und entfernt sie. * Prüft einen Punkt in drei Y-Höhen gegen alle Tore.
* Nutzt mehrere Erkennungsmethoden für maximale Sicherheit. * @return true wenn ein Tor gewertet wurde (Schleife soll abbrechen)
*/ */
private void removeAllOldBallsGlobal() { private boolean checkPoint(double x, double y, double z, World world) {
int removed = 0; for (double yOff : new double[]{0.0, 0.45, 0.9}) {
// Alle Welten durchsuchen Location check = new Location(world, x, y + yOff, z);
for (World world : Bukkit.getWorlds()) { for (SoccerGoal goal : goals.values()) {
for (Entity entity : world.getEntities()) { if (goal.contains(check)) {
if (entity instanceof ArmorStand stand) { scoreGoal(goal);
boolean shouldRemove = false; return true;
// Methode 1: Tag-basiert
if (stand.getScoreboardTags().contains(BALL_TAG)) {
shouldRemove = true;
}
// Methode 2: Name-basiert (falls Tags verloren gehen)
if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) {
shouldRemove = true;
}
// Methode 3: Kopf-Textur-basiert (prüfe ob es ein Soccer-Ball Kopf ist)
if (stand.getEquipment() != null && stand.getEquipment().getHelmet() != null) {
ItemStack helmet = stand.getEquipment().getHelmet();
if (helmet.getType() == Material.PLAYER_HEAD && helmet.hasItemMeta()) {
SkullMeta meta = (SkullMeta) helmet.getItemMeta();
if (meta.hasOwner() && meta.getOwnerProfile() != null) {
PlayerProfile profile = meta.getOwnerProfile();
if (profile.getName() != null && profile.getName().equals("SoccerBall")) {
shouldRemove = true;
}
}
}
}
// Methode 4: Position-basiert - Entferne alle unsichtbaren, kleinen ArmorStands in der Nähe des Spawns
if (spawnLocation != null && spawnLocation.getWorld() != null &&
stand.getWorld().equals(spawnLocation.getWorld()) &&
stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) {
double distance = stand.getLocation().distance(spawnLocation);
// Wenn innerhalb von 5 Blöcken vom Spawn und hat einen Kopf
if (distance < 5.0 && stand.getEquipment() != null &&
stand.getEquipment().getHelmet() != null &&
stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) {
shouldRemove = true;
}
}
// Nur entfernen wenn es NICHT unsere aktuelle Ball-Instanz ist
if (shouldRemove && (ball == null || !stand.getUniqueId().equals(ball.getUniqueId()))) {
stand.remove();
removed++;
}
} }
} }
} }
if (removed > 0) { return false;
Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt."); }
// =========================================================================
// TOR WERTEN
// =========================================================================
private void scoreGoal(SoccerGoal goal) {
if (System.currentTimeMillis() - lastGoalTime < GOAL_COOLDOWN_MS) return;
lastGoalTime = System.currentTimeMillis();
scores.merge(goal.team, 1, Integer::sum);
String teamColor = goal.team == 1 ? "§9" : "§c";
String teamName = goal.team == 1 ? "Team Blau" : "Team Rot";
if (ball != null && ball.isValid()) ball.setVelocity(new Vector(0, 0, 0));
prevPos = null; // Reset damit kein Doppel-Trigger aus altem Segment
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendTitle(teamColor + "§lTOR!", teamColor + teamName + " §8| §fStand: " + getScoreString(), 5, 50, 15);
p.sendMessage("§8[§6Soccer§8] " + teamColor + "§lTOR! §r§7für " + teamName + " §8| §fStand: " + getScoreString());
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f);
}
spawnGoalCelebration(goal);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::respawnBall, GOAL_RESPAWN_DELAY);
}
private void spawnGoalCelebration(SoccerGoal goal) {
Location center = goal.getCenter();
World world = center.getWorld();
if (world == null) return;
for (int wave = 0; wave < 6; wave++) {
final double offsetY = wave * 0.25;
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
Location wLoc = center.clone().add(0, offsetY, 0);
world.spawnParticle(Particle.DUST, wLoc, 50, 1.2, 0.6, 1.2, 0, DUST_GOAL_FLASH);
world.spawnParticle(Particle.FIREWORK, wLoc, 25, 1.0, 0.8, 1.0, 0.25);
world.spawnParticle(Particle.EXPLOSION, center, 2, 0.4, 0.4, 0.4, 0.05);
}, wave * 7L);
}
}
// =========================================================================
// PHYSIK
// =========================================================================
private void handleDribbling() {
if (ball == null || !ball.isValid()) return;
for (Entity nearby : ball.getNearbyEntities(DRIBBLE_DETECTION_RADIUS, DRIBBLE_HEIGHT, DRIBBLE_DETECTION_RADIUS)) {
if (nearby instanceof Player player) {
Vector dir = ball.getLocation().toVector().subtract(player.getLocation().toVector());
if (dir.lengthSquared() > 0) {
dir.normalize().setY(DRIBBLE_LIFT);
ball.setVelocity(dir.multiply(DRIBBLE_FORCE));
lastMoveTime = System.currentTimeMillis();
}
}
} }
} }
private void handleWallBounce(Vector vel) { private void handleWallBounce(Vector vel) {
if (vel.lengthSquared() < 0.001) return; if (vel.lengthSquared() < 0.001) return;
Location loc = ball.getLocation(); Location loc = ball.getLocation();
Block nextX = loc.clone().add(vel.getX() * 1.3, 0.5, 0).getBlock(); Block nx = loc.clone().add(vel.getX() * WALL_CHECK_DISTANCE, 0.5, 0).getBlock();
Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * 1.3).getBlock(); Block nz = loc.clone().add(0, 0.5, vel.getZ() * WALL_CHECK_DISTANCE).getBlock();
boolean bounced = false; boolean bounced = false;
if (nextX.getType().isSolid()) { vel.setX(-vel.getX() * 0.75); bounced = true; } if (nx.getType().isSolid()) { vel.setX(-vel.getX() * WALL_BOUNCE_DAMPING); bounced = true; }
if (nextZ.getType().isSolid()) { vel.setZ(-vel.getZ() * 0.75); bounced = true; } if (nz.getType().isSolid()) { vel.setZ(-vel.getZ() * WALL_BOUNCE_DAMPING); bounced = true; }
if (bounced) { if (bounced) {
ball.setVelocity(vel); ball.setVelocity(vel);
ball.getWorld().playSound(ball.getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 1.3f); ball.getWorld().playSound(loc, Sound.BLOCK_WOOD_BREAK, 0.6f, 1.3f);
} }
} }
private void handleParticles(double speed) { private void handleParticles(double speed) {
if (speed < 0.05) return; if (speed < PARTICLE_SPEED_MIN) return;
Location loc = ball.getLocation().add(0, 0.2, 0); Location loc = ball.getLocation().add(0, 0.2, 0);
World world = loc.getWorld(); World w = loc.getWorld();
if (world == null) return; if (w == null) return;
if (speed > PARTICLE_SPEED_HIGH) w.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0);
if (speed > 0.85) world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0); else if (speed > PARTICLE_SPEED_MEDIUM) w.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08);
else if (speed > 0.45) world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08); else w.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
else world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
} }
// =========================================================================
// TOR-PARTIKEL ZEICHNEN
// =========================================================================
private void drawAllGoalParticles() {
for (SoccerGoal goal : goals.values()) drawGoalFrame(goal);
}
private void drawGoalFrame(SoccerGoal goal) {
World world = goal.getWorld();
if (world == null) return;
Particle.DustOptions dust = goal.team == 1 ? DUST_TEAM1 : DUST_TEAM2;
double x1 = Math.min(goal.pos1.getX(), goal.pos2.getX()), x2 = Math.max(goal.pos1.getX(), goal.pos2.getX());
double y1 = Math.min(goal.pos1.getY(), goal.pos2.getY()), y2 = Math.max(goal.pos1.getY(), goal.pos2.getY());
double z1 = Math.min(goal.pos1.getZ(), goal.pos2.getZ()), z2 = Math.max(goal.pos1.getZ(), goal.pos2.getZ());
line(world, x1,y1,z1, x2,y1,z1, dust); line(world, x1,y1,z2, x2,y1,z2, dust);
line(world, x1,y1,z1, x1,y1,z2, dust); line(world, x2,y1,z1, x2,y1,z2, dust);
line(world, x1,y2,z1, x2,y2,z1, dust); line(world, x1,y2,z2, x2,y2,z2, dust);
line(world, x1,y2,z1, x1,y2,z2, dust); line(world, x2,y2,z1, x2,y2,z2, dust);
line(world, x1,y1,z1, x1,y2,z1, dust); line(world, x2,y1,z1, x2,y2,z1, dust);
line(world, x1,y1,z2, x1,y2,z2, dust); line(world, x2,y1,z2, x2,y2,z2, dust);
}
private void line(World world, double ax, double ay, double az,
double bx, double by, double bz, Particle.DustOptions dust) {
double dx = bx-ax, dy = by-ay, dz = bz-az;
double len = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (len < 0.01) return;
int steps = (int) Math.ceil(len / PARTICLE_STEP);
dx /= steps; dy /= steps; dz /= steps;
for (int i = 0; i <= steps; i++)
world.spawnParticle(Particle.DUST, ax+dx*i, ay+dy*i, az+dz*i, 1, 0,0,0,0, dust, true);
}
// =========================================================================
// DATEN-KLASSE: SoccerGoal
// =========================================================================
private static class SoccerGoal {
final String name; final Location pos1, pos2; final int team;
SoccerGoal(String name, Location pos1, Location pos2, int team) {
this.name = name; this.pos1 = pos1; this.pos2 = pos2; this.team = team;
}
boolean contains(Location loc) {
if (loc.getWorld() == null || !loc.getWorld().equals(pos1.getWorld())) return false;
return loc.getX() >= Math.min(pos1.getX(), pos2.getX())
&& loc.getX() <= Math.max(pos1.getX(), pos2.getX())
&& loc.getY() >= Math.min(pos1.getY(), pos2.getY())
&& loc.getY() <= Math.max(pos1.getY(), pos2.getY())
&& loc.getZ() >= Math.min(pos1.getZ(), pos2.getZ())
&& loc.getZ() <= Math.max(pos1.getZ(), pos2.getZ());
}
Location getCenter() {
return new Location(pos1.getWorld(),
(pos1.getX()+pos2.getX())/2.0,
(pos1.getY()+pos2.getY())/2.0,
(pos1.getZ()+pos2.getZ())/2.0);
}
World getWorld() { return pos1.getWorld(); }
}
// =========================================================================
// CONFIG: TORE
// =========================================================================
private void loadGoals() {
goals.clear();
FileConfiguration config = NexusLobby.getInstance().getConfig();
ConfigurationSection section = config.getConfigurationSection("ball.goals");
if (section == null) return;
for (String name : section.getKeys(false)) {
String path = "ball.goals." + name;
Location pos1 = loadLoc(config, path + ".pos1");
Location pos2 = loadLoc(config, path + ".pos2");
if (pos1 == null || pos2 == null) continue;
goals.put(name, new SoccerGoal(name, pos1, pos2, config.getInt(path + ".team", 1)));
}
Bukkit.getLogger().info("[Soccer] " + goals.size() + " Tor(e) geladen.");
}
private void saveGoal(SoccerGoal g) {
String path = "ball.goals." + g.name;
FileConfiguration c = NexusLobby.getInstance().getConfig();
saveLoc(c, path + ".pos1", g.pos1); saveLoc(c, path + ".pos2", g.pos2);
c.set(path + ".team", g.team);
NexusLobby.getInstance().saveConfig();
}
private void deleteGoal(String name) {
NexusLobby.getInstance().getConfig().set("ball.goals." + name, null);
NexusLobby.getInstance().saveConfig();
}
private static void saveLoc(FileConfiguration c, String path, Location loc) {
if (loc == null || loc.getWorld() == null) return;
c.set(path + ".world", loc.getWorld().getName());
c.set(path + ".x", loc.getX()); c.set(path + ".y", loc.getY()); c.set(path + ".z", loc.getZ());
}
private static Location loadLoc(FileConfiguration c, String path) {
String wName = c.getString(path + ".world");
if (wName == null) return null;
World world = Bukkit.getWorld(wName);
if (world == null) { Bukkit.getLogger().warning("[Soccer] Welt nicht gefunden: " + wName); return null; }
return new Location(world, c.getDouble(path + ".x"), c.getDouble(path + ".y"), c.getDouble(path + ".z"));
}
// =========================================================================
// BALL SPAWNEN / RESPAWNEN
// =========================================================================
private void spawnBall() { private void spawnBall() {
if (spawnLocation == null || (ball != null && ball.isValid())) return; if (spawnLocation == null || spawnLocation.getWorld() == null) return;
if (ball != null && ball.isValid()) return;
// Ball direkt auf dem Boden spawnen (keine Y-Offset Erhöhung) ball = (ArmorStand) spawnLocation.getWorld().spawnEntity(spawnLocation.clone(), EntityType.ARMOR_STAND);
Location spawnLoc = spawnLocation.clone(); ball.setInvisible(true); ball.setGravity(true); ball.setBasePlate(false);
ball = (ArmorStand) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.ARMOR_STAND); ball.setSmall(true); ball.setInvulnerable(false); ball.setArms(false);
ball.setCustomNameVisible(false); ball.setCustomName(BALL_NAME);
ball.setInvisible(true); ball.setPersistent(false); ball.addScoreboardTag(BALL_TAG);
ball.setGravity(true); if (ball.getEquipment() != null) ball.getEquipment().setHelmet(getSoccerHead());
ball.setBasePlate(false); prevPos = spawnLocation.clone();
ball.setSmall(true);
ball.setInvulnerable(false);
ball.setArms(false);
ball.setCustomNameVisible(false);
// Setze Custom Name für zusätzliche Erkennung (unsichtbar für Spieler)
ball.setCustomName(BALL_NAME);
// Deaktiviert das Speichern in der Welt-Datei
ball.setPersistent(false);
// Markiert den Ball für das Cleanup-System
ball.addScoreboardTag(BALL_TAG);
ItemStack ballHead = getSoccerHead();
if (ball.getEquipment() != null) ball.getEquipment().setHelmet(ballHead);
lastMoveTime = System.currentTimeMillis(); lastMoveTime = System.currentTimeMillis();
} }
public void respawnBall() {
if (ball != null && ball.isValid()) { ball.remove(); ball = null; }
removeAllOldBalls();
prevPos = null;
spawnBall();
}
private ItemStack getSoccerHead() { private ItemStack getSoccerHead() {
ItemStack head = new ItemStack(Material.PLAYER_HEAD); ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta(); SkullMeta meta = (SkullMeta) head.getItemMeta();
if (meta == null) return head; if (meta == null) return head;
PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), BALL_NAME);
PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), "SoccerBall"); try { profile.getTextures().setSkin(new URL(TEXTURE_URL)); }
try { catch (MalformedURLException e) { Bukkit.getLogger().warning("[NexusLobby] Ungültige Ball-Textur URL!"); }
profile.getTextures().setSkin(new URL(TEXTURE_URL));
} catch (MalformedURLException ignored) {}
meta.setOwnerProfile(profile); meta.setOwnerProfile(profile);
head.setItemMeta(meta); head.setItemMeta(meta);
return head; return head;
} }
public void respawnBall() { // =========================================================================
if (ball != null) { // CLEANUP
ball.remove(); // =========================================================================
ball = null;
private void removeDuplicateBalls() {
if (ball == null || !ball.isValid() || ball.getLocation().getWorld() == null) return;
for (Entity e : ball.getLocation().getWorld().getNearbyEntities(ball.getLocation(), 50, 50, 50)) {
if (e instanceof ArmorStand s && isBallEntity(s) && !s.getUniqueId().equals(ball.getUniqueId()))
s.remove();
} }
removeAllOldBallsGlobal();
spawnBall();
} }
private void removeAllOldBalls() {
int removed = 0;
if (spawnLocation != null && spawnLocation.getWorld() != null)
removed = cleanupWorld(spawnLocation.getWorld());
else
for (World w : Bukkit.getWorlds()) removed += cleanupWorld(w);
if (removed > 0) Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt.");
}
private int cleanupWorld(World world) {
int n = 0;
for (Entity e : world.getEntities()) {
if (e instanceof ArmorStand s && isBallEntity(s)
&& (ball == null || !s.getUniqueId().equals(ball.getUniqueId()))) {
s.remove(); n++;
}
}
return n;
}
private boolean isBallEntity(ArmorStand s) {
if (s.getScoreboardTags().contains(BALL_TAG)) return true;
if (BALL_NAME.equals(s.getCustomName())) return true;
if (s.getEquipment() != null && s.getEquipment().getHelmet() != null) {
ItemStack h = s.getEquipment().getHelmet();
if (h.getType() == Material.PLAYER_HEAD && h.hasItemMeta()) {
SkullMeta m = (SkullMeta) h.getItemMeta();
if (m.hasOwner() && m.getOwnerProfile() != null
&& BALL_NAME.equals(m.getOwnerProfile().getName())) return true;
}
}
if (spawnLocation != null && spawnLocation.getWorld() != null
&& s.getWorld().equals(spawnLocation.getWorld())
&& s.isSmall() && s.isInvisible() && !s.hasBasePlate()
&& s.getLocation().distance(spawnLocation) < CLEANUP_RADIUS
&& s.getEquipment() != null && s.getEquipment().getHelmet() != null
&& s.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD)
return true;
return false;
}
// =========================================================================
// EVENTS
// =========================================================================
@EventHandler @EventHandler
public void onBallPunch(EntityDamageByEntityEvent event) { public void onBallPunch(EntityDamageByEntityEvent event) {
if (event.getEntity().equals(ball)) { if (ball == null || !event.getEntity().equals(ball)) return;
event.setCancelled(true); event.setCancelled(true);
if (event.getDamager() instanceof Player p) { if (!(event.getDamager() instanceof Player p)) return;
Vector shootDir = p.getLocation().getDirection();
if (shootDir.lengthSquared() > 0) { Vector dir = p.getLocation().getDirection().normalize();
shootDir.normalize().multiply(1.35).setY(0.38); dir.multiply(KICK_FORCE).setY(KICK_LIFT);
ball.setVelocity(shootDir);
} // prevPos auf aktuelle Position setzen bevor der Ball sich bewegt
ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f); // so startet das nächste Segment am richtigen Punkt.
lastMoveTime = System.currentTimeMillis(); prevPos = ball.getLocation().clone();
} ball.setVelocity(dir);
}
ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f);
lastMoveTime = System.currentTimeMillis();
} }
@EventHandler @EventHandler
public void onBallInteract(PlayerInteractAtEntityEvent event) { public void onBallInteract(PlayerInteractAtEntityEvent event) {
if (event.getRightClicked().equals(ball)) event.setCancelled(true); if (ball != null && event.getRightClicked().equals(ball)) event.setCancelled(true);
} }
private void loadConfigLocation() { // =========================================================================
FileConfiguration config = NexusLobby.getInstance().getConfig(); // COMMANDS
if (config.contains("ball.spawn")) spawnLocation = config.getLocation("ball.spawn"); // =========================================================================
}
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player p) || !p.hasPermission("nexuslobby.admin")) return true; if (!(sender instanceof Player p)) { sender.sendMessage("§cNur Spieler."); return true; }
if (!p.hasPermission("nexuslobby.admin")) { p.sendMessage("§cKeine Berechtigung!"); return true; }
if (args.length < 2 || !args[0].equalsIgnoreCase("ball")) return false;
if (args.length >= 2 && args[0].equalsIgnoreCase("ball")) { switch (args[1].toLowerCase()) {
if (args[1].equalsIgnoreCase("setspawn")) { case "setspawn" -> cmdSetSpawn(p);
spawnLocation = p.getLocation(); case "respawn" -> { respawnBall(); p.sendMessage("§8[§6Soccer§8] §eBall neu gespawnt."); }
NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation); case "remove" -> cmdRemove(p);
NexusLobby.getInstance().saveConfig(); case "goal" -> cmdGoal(p, args);
respawnBall(); case "score" -> cmdScore(p, args);
p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt. Cleanup ist aktiv!"); default -> sendHelp(p);
return true;
} else if (args[1].equalsIgnoreCase("respawn")) {
respawnBall();
p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt.");
return true;
}
} }
return false; return true;
} }
@Override private void cmdSetSpawn(Player p) {
public void onDisable() { spawnLocation = p.getLocation();
if (ball != null) ball.remove(); FileConfiguration c = NexusLobby.getInstance().getConfig();
c.set("ball.spawn.world", spawnLocation.getWorld().getName());
c.set("ball.spawn.x", spawnLocation.getX());
c.set("ball.spawn.y", spawnLocation.getY());
c.set("ball.spawn.z", spawnLocation.getZ());
NexusLobby.getInstance().saveConfig();
respawnBall();
p.sendMessage("§8[§6Soccer§8] §aBall-Spawn gesetzt und Ball respawnt!");
}
private void cmdRemove(Player p) {
if (ball != null) { ball.remove(); ball = null; }
removeAllOldBalls();
p.sendMessage("§8[§6Soccer§8] §cBall entfernt.");
}
private void cmdGoal(Player p, String[] args) {
if (args.length < 3) { sendGoalHelp(p); return; }
switch (args[2].toLowerCase()) {
case "pos1" -> {
selPos1.put(p.getUniqueId(), p.getLocation().clone());
p.sendMessage("§8[§6Soccer§8] §aPos1 §7gesetzt bei §e" + fmt(p.getLocation()));
p.sendMessage("§8[§6Soccer§8] §7Zur gegenüberliegenden Ecke und §e/nexuslobby ball goal pos2§7 eingeben.");
}
case "pos2" -> {
selPos2.put(p.getUniqueId(), p.getLocation().clone());
p.sendMessage("§8[§6Soccer§8] §aPos2 §7gesetzt bei §e" + fmt(p.getLocation()));
p.sendMessage("§8[§6Soccer§8] §7Speichern mit §e/nexuslobby ball goal save <n> <1|2>§7.");
}
case "save" -> {
if (args.length < 5) { p.sendMessage("§cVerwendung: /nexuslobby ball goal save <n> <1|2>"); return; }
Location p1 = selPos1.get(p.getUniqueId());
Location p2 = selPos2.get(p.getUniqueId());
if (p1 == null || p2 == null) { p.sendMessage("§cZuerst pos1 und pos2 setzen!"); return; }
int team;
try { team = Integer.parseInt(args[4]); }
catch (NumberFormatException e) { p.sendMessage("§cTeam muss 1 oder 2 sein!"); return; }
if (team != 1 && team != 2) { p.sendMessage("§cTeam muss 1 §9(Blau)§c oder 2 §c(Rot) sein!"); return; }
String name = args[3];
SoccerGoal goal = new SoccerGoal(name, p1, p2, team);
goals.put(name, goal);
saveGoal(goal);
selPos1.remove(p.getUniqueId()); selPos2.remove(p.getUniqueId());
p.sendMessage("§8[§6Soccer§8] §aTor §e" + name + " §afür " + (team==1?"§9":"§c") + "Team " + team + " §aerstellt!");
p.sendMessage("§8[§6Soccer§8] §7Nutze §e/nexuslobby ball goal show §7um die Box zu sehen.");
drawGoalFrame(goal);
}
case "delete" -> {
if (args.length < 4) { p.sendMessage("§cVerwendung: /nexuslobby ball goal delete <n>"); return; }
String name = args[3];
if (goals.remove(name) != null) {
deleteGoal(name);
p.sendMessage("§8[§6Soccer§8] §cTor §e" + name + " §cgelöscht.");
} else {
p.sendMessage("§cTor '" + name + "' nicht gefunden. Tore: " + String.join(", ", goals.keySet()));
}
}
case "list" -> {
if (goals.isEmpty()) { p.sendMessage("§8[§6Soccer§8] §7Keine Tore gesetzt."); return; }
p.sendMessage("§8[§6Soccer§8] §6Registrierte Tore §8(" + goals.size() + ")§6:");
goals.forEach((name, g) -> p.sendMessage(
"§8 » §e" + name + " §8— " + (g.team==1?"§9Blau":"§cRot") +
" §8| §7" + fmt(g.pos1) + " §8→ §7" + fmt(g.pos2)));
}
case "show" -> {
for (int i = 0; i < 10; i++) {
final int tick = i * 4;
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::drawAllGoalParticles, tick);
}
p.sendMessage("§8[§6Soccer§8] §aTore werden für ~2 Sekunden angezeigt.");
}
case "debug" -> {
if (ball == null || !ball.isValid()) { p.sendMessage("§8[§6Soccer§8] §cKein Ball."); return; }
Location bl = ball.getLocation();
Vector bv = ball.getVelocity();
p.sendMessage("§8[§6Soccer§8] §6Ball getLocation(): §f" + fmtFull(bl));
p.sendMessage("§8[§6Soccer§8] §6Ball getVelocity(): §f" + fmtVec(bv) + " §7(speed=" + String.format("%.4f", bv.length()) + ")");
p.sendMessage("§8[§6Soccer§8] §6prevPos: §f" + (prevPos != null ? fmtFull(prevPos) : "null"));
if (!goals.isEmpty()) {
p.sendMessage("§8[§6Soccer§8] §7Direkter Punkt-Check:");
for (SoccerGoal g : goals.values()) {
boolean h0 = g.contains(bl.clone());
boolean h1 = g.contains(bl.clone().add(0, 0.45, 0));
boolean h2 = g.contains(bl.clone().add(0, 0.9, 0));
p.sendMessage(" §e" + g.name + " §7Fuß=" + cb(h0) + " Mitte=" + cb(h1) + " Kopf=" + cb(h2));
}
}
}
default -> sendGoalHelp(p);
}
}
private void cmdScore(Player p, String[] args) {
if (args.length >= 3 && args[2].equalsIgnoreCase("reset")) {
scores.put(1, 0);
scores.put(2, 0);
for (Player pl : Bukkit.getOnlinePlayers()) {
pl.sendMessage("§8[§6Soccer§8] §eStand wurde zurückgesetzt! §8| §fNeuer Stand: " + getScoreString());
pl.sendTitle("§eStand zurückgesetzt!", "§90 §8: §c0", 5, 40, 10);
pl.playSound(pl.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.2f);
}
Bukkit.getLogger().info("[Soccer] Stand von " + p.getName() + " zurückgesetzt.");
} else {
p.sendMessage("§8[§6Soccer§8] §6Aktueller Stand: §f" + getScoreString());
p.sendMessage("§8[§6Soccer§8] §7Reset: §e/nexuslobby ball score reset");
}
}
// =========================================================================
// HILFSMETHODEN
// =========================================================================
private String getScoreString() {
return "§9" + scores.getOrDefault(1, 0) + " §8: §c" + scores.getOrDefault(2, 0);
}
private static String fmt(Location l) {
return String.format("%.0f/%.0f/%.0f", l.getX(), l.getY(), l.getZ());
}
private static String fmtFull(Location l) {
return String.format("%.3f/%.3f/%.3f", l.getX(), l.getY(), l.getZ());
}
private static String fmtVec(Vector v) {
return String.format("%.3f/%.3f/%.3f", v.getX(), v.getY(), v.getZ());
}
private static String cb(boolean b) { return b ? "§atrue §r" : "§cfalse §r"; }
private void loadConfigLocation() {
FileConfiguration c = NexusLobby.getInstance().getConfig();
if (!c.contains("ball.spawn.world")) return;
String wName = c.getString("ball.spawn.world");
World world = Bukkit.getWorld(wName);
if (world == null) {
Bukkit.getLogger().warning("[Soccer] Spawn-Welt '" + wName + "' nicht gefunden!");
return;
}
spawnLocation = new Location(world,
c.getDouble("ball.spawn.x"), c.getDouble("ball.spawn.y"), c.getDouble("ball.spawn.z"));
}
private void sendHelp(Player p) {
p.sendMessage("§8§m─────────────§r §6§lSoccer §r§8§m─────────────");
p.sendMessage("§e/nexuslobby ball setspawn §7Ball-Spawn setzen");
p.sendMessage("§e/nexuslobby ball respawn §7Ball neu spawnen");
p.sendMessage("§e/nexuslobby ball remove §7Ball entfernen");
p.sendMessage("§e/nexuslobby ball goal ... §7Tore verwalten");
p.sendMessage("§e/nexuslobby ball score §7Stand anzeigen");
p.sendMessage("§e/nexuslobby ball score reset §7Stand zurücksetzen");
p.sendMessage("§8§m─────────────────────────────────────────");
}
private void sendGoalHelp(Player p) {
p.sendMessage("§8§m─────────────§r §6§lTore einrichten §r§8§m─────────────");
p.sendMessage("§71. §7Stehe in eine Ecke des Tors:");
p.sendMessage(" §e/nexuslobby ball goal pos1");
p.sendMessage("§72. §7Zur gegenüberliegenden Ecke gehen:");
p.sendMessage(" §e/nexuslobby ball goal pos2");
p.sendMessage("§73. §7Speichern §8(Team 1=§9Blau§8, 2=§cRot§8)§7:");
p.sendMessage(" §e/nexuslobby ball goal save <n> <1|2>");
p.sendMessage("§e/nexuslobby ball goal delete <n> §7Tor löschen");
p.sendMessage("§e/nexuslobby ball goal list §7Alle Tore");
p.sendMessage("§e/nexuslobby ball goal show §7Tore anzeigen");
p.sendMessage("§e/nexuslobby ball goal debug §7Ball-Position debuggen");
p.sendMessage("§8§m─────────────────────────────────────────────────");
} }
} }

View File

@@ -40,10 +40,21 @@ public class BorderCommand implements CommandExecutor {
} }
try { try {
double radius = Double.parseDouble(args[1]); double radius = Double.parseDouble(args[1]);
Location loc = p.getLocation();
config.set("worldborder.type", "CIRCLE"); config.set("worldborder.type", "CIRCLE");
config.set("worldborder.center", p.getLocation());
config.set("worldborder.radius", radius);
config.set("worldborder.enabled", true); config.set("worldborder.enabled", true);
config.set("worldborder.radius", radius);
// FIX: Location als einzelne Werte speichern statt als Objekt.
// config.set(key, Location) ist nach Neustarts unzuverlässig,
// weil config.getLocation() die Welt zum Deserialisierungs-
// zeitpunkt auflösen muss und das fehlschlagen kann.
config.set("worldborder.center.world", loc.getWorld().getName());
config.set("worldborder.center.x", loc.getX());
config.set("worldborder.center.y", loc.getY());
config.set("worldborder.center.z", loc.getZ());
p.sendMessage("§8[§6Nexus§8] §aKreis-Grenze (Radius: " + radius + ") gesetzt."); p.sendMessage("§8[§6Nexus§8] §aKreis-Grenze (Radius: " + radius + ") gesetzt.");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
p.sendMessage("§cUngültige Zahl."); p.sendMessage("§cUngültige Zahl.");
@@ -57,10 +68,21 @@ public class BorderCommand implements CommandExecutor {
p.sendMessage("§8[§6Nexus§8] §cBitte markiere erst 2 Punkte mit der Portalwand!"); p.sendMessage("§8[§6Nexus§8] §cBitte markiere erst 2 Punkte mit der Portalwand!");
return true; return true;
} }
config.set("worldborder.type", "SQUARE"); config.set("worldborder.type", "SQUARE");
config.set("worldborder.pos1", l1);
config.set("worldborder.pos2", l2);
config.set("worldborder.enabled", true); config.set("worldborder.enabled", true);
// FIX: Beide Positionen als einzelne Werte speichern
config.set("worldborder.pos1.world", l1.getWorld().getName());
config.set("worldborder.pos1.x", l1.getX());
config.set("worldborder.pos1.y", l1.getY());
config.set("worldborder.pos1.z", l1.getZ());
config.set("worldborder.pos2.world", l2.getWorld().getName());
config.set("worldborder.pos2.x", l2.getX());
config.set("worldborder.pos2.y", l2.getY());
config.set("worldborder.pos2.z", l2.getZ());
p.sendMessage("§8[§6Nexus§8] §aViereckige Grenze erfolgreich gesetzt."); p.sendMessage("§8[§6Nexus§8] §aViereckige Grenze erfolgreich gesetzt.");
} }
case "disable" -> { case "disable" -> {

View File

@@ -9,19 +9,29 @@ import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
/** /**
* Verwaltet die Lobby-Begrenzung (Kreis oder Rechteck). * Verwaltet die Lobby-Begrenzung (Kreis oder Rechteck).
* Speichert Daten in der Haupt-config.yml unter 'worldborder'. * Speichert Daten in der Haupt-config.yml unter 'worldborder'.
*
* FIX: Locations werden als einzelne Werte (worldName, x, y, z) gespeichert
* statt als serialisiertes Location-Objekt, da config.getLocation()
* nach einem Neustart unzuverlässig ist.
*/ */
public class BorderModule implements Module, Listener { public class BorderModule implements Module, Listener {
private String type; private String type;
private Location pos1, pos2, center; // FIX: Statt Location-Objekte die rohen Werte cachen
private String pos1World, pos2World, centerWorld;
private double pos1X, pos1Y, pos1Z;
private double pos2X, pos2Y, pos2Z;
private double centerX, centerZ;
private double radius; private double radius;
private boolean enabled; private boolean enabled;
private boolean hasPos1, hasPos2, hasCenter;
@Override @Override
public String getName() { return "WorldBorder"; } public String getName() { return "WorldBorder"; }
@@ -34,74 +44,96 @@ public class BorderModule implements Module, Listener {
@Override @Override
public void onDisable() { public void onDisable() {
// Aufräumarbeiten falls nötig // FIX: Listener beim Deaktivieren abmelden, damit nach /reload
// keine doppelten Events gefeuert werden.
HandlerList.unregisterAll(this);
} }
/** /**
* Lädt die Border-Einstellungen aus der config.yml. * Lädt die Border-Einstellungen aus der config.yml.
* Wird auch von NexusLobby.reloadPlugin() aufgerufen. * Wird auch von BorderCommand und NexusLobby.reloadPlugin() aufgerufen.
*
* FIX: Robuste Methode - liest world-Name und einzelne Koordinaten,
* kein config.getLocation() mehr (das nach Neustarts versagen kann).
*/ */
public void reloadConfig() { public void reloadConfig() {
FileConfiguration config = NexusLobby.getInstance().getConfig(); FileConfiguration config = NexusLobby.getInstance().getConfig();
// Pfad in der config.yml: worldborder.enabled etc.
this.enabled = config.getBoolean("worldborder.enabled", false); this.enabled = config.getBoolean("worldborder.enabled", false);
this.type = config.getString("worldborder.type", "CIRCLE"); this.type = config.getString("worldborder.type", "CIRCLE").toUpperCase();
this.radius = config.getDouble("worldborder.radius", 50.0); this.radius = config.getDouble("worldborder.radius", 50.0);
// Locations laden // --- CIRCLE-Daten laden ---
this.center = config.getLocation("worldborder.center"); this.hasCenter = config.contains("worldborder.center.world");
this.pos1 = config.getLocation("worldborder.pos1"); if (hasCenter) {
this.pos2 = config.getLocation("worldborder.pos2"); this.centerWorld = config.getString("worldborder.center.world");
this.centerX = config.getDouble("worldborder.center.x");
this.centerZ = config.getDouble("worldborder.center.z");
}
// --- SQUARE-Daten laden ---
this.hasPos1 = config.contains("worldborder.pos1.world");
this.hasPos2 = config.contains("worldborder.pos2.world");
if (hasPos1) {
this.pos1World = config.getString("worldborder.pos1.world");
this.pos1X = config.getDouble("worldborder.pos1.x");
this.pos1Y = config.getDouble("worldborder.pos1.y");
this.pos1Z = config.getDouble("worldborder.pos1.z");
}
if (hasPos2) {
this.pos2World = config.getString("worldborder.pos2.world");
this.pos2X = config.getDouble("worldborder.pos2.x");
this.pos2Y = config.getDouble("worldborder.pos2.y");
this.pos2Z = config.getDouble("worldborder.pos2.z");
}
} }
@EventHandler @EventHandler
public void onMove(PlayerMoveEvent event) { public void onMove(PlayerMoveEvent event) {
if (!enabled || event.getTo() == null) return; if (!enabled || event.getTo() == null) return;
// Performance: Nur prüfen, wenn sich die Block-Koordinaten ändern // Performance: Nur prüfen wenn sich Block-Koordinaten ändern
if (event.getFrom().getBlockX() == event.getTo().getBlockX() && if (event.getFrom().getBlockX() == event.getTo().getBlockX() &&
event.getFrom().getBlockZ() == event.getTo().getBlockZ() && event.getFrom().getBlockZ() == event.getTo().getBlockZ() &&
event.getFrom().getBlockY() == event.getTo().getBlockY()) return; event.getFrom().getBlockY() == event.getTo().getBlockY()) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
// Admins und Spectators ignorieren // FIX: nexuslobby.border.bypass statt nur nexuslobby.admin prüfen
if (player.hasPermission("nexuslobby.admin") || player.getGameMode().name().equals("SPECTATOR")) return; if (player.hasPermission("nexuslobby.border.bypass") ||
player.hasPermission("nexuslobby.admin") ||
player.getGameMode().name().equals("SPECTATOR")) return;
Location to = event.getTo(); Location to = event.getTo();
boolean outside = false; boolean outside = false;
// --- Prüfung: Kreis-Border --- // --- Prüfung: Kreis-Border ---
if (type.equalsIgnoreCase("CIRCLE") && center != null) { if (type.equals("CIRCLE") && hasCenter) {
if (to.getWorld().equals(center.getWorld())) { World cWorld = Bukkit.getWorld(centerWorld);
double distSq = Math.pow(to.getX() - center.getX(), 2) + Math.pow(to.getZ() - center.getZ(), 2); if (cWorld != null && to.getWorld().equals(cWorld)) {
if (distSq > Math.pow(radius, 2)) outside = true; double distSq = Math.pow(to.getX() - centerX, 2) + Math.pow(to.getZ() - centerZ, 2);
if (distSq > radius * radius) outside = true;
} }
} }
// --- Prüfung: Rechteck-Border (Square) --- // --- Prüfung: Rechteck-Border (Square) ---
else if (type.equalsIgnoreCase("SQUARE") && pos1 != null && pos2 != null) { else if (type.equals("SQUARE") && hasPos1 && hasPos2) {
if (to.getWorld().equals(pos1.getWorld())) { World bWorld = Bukkit.getWorld(pos1World);
double minX = Math.min(pos1.getX(), pos2.getX()); if (bWorld != null && to.getWorld().equals(bWorld)) {
double maxX = Math.max(pos1.getX(), pos2.getX()); double minX = Math.min(pos1X, pos2X);
double minZ = Math.min(pos1.getZ(), pos2.getZ()); double maxX = Math.max(pos1X, pos2X);
double maxZ = Math.max(pos1.getZ(), pos2.getZ()); double minZ = Math.min(pos1Z, pos2Z);
double maxZ = Math.max(pos1Z, pos2Z);
if (to.getX() < minX || to.getX() > maxX || to.getZ() < minZ || to.getZ() > maxZ) { if (to.getX() < minX || to.getX() > maxX ||
to.getZ() < minZ || to.getZ() > maxZ) {
outside = true; outside = true;
} }
} }
} }
// --- Aktion wenn außerhalb ---
if (outside) { if (outside) {
Location spawnLocation = getMainSpawnLocation(); Location spawnLocation = getMainSpawnLocation();
if (spawnLocation != null) { if (spawnLocation != null) {
// Sofortiger Teleport zum definierten Spawn
player.teleport(spawnLocation); player.teleport(spawnLocation);
// Feedback an den Spieler
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f); player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f);
player.sendMessage("§8[§6Nexus§8] §cDu hast den Lobby-Bereich verlassen!"); player.sendMessage("§8[§6Nexus§8] §cDu hast den Lobby-Bereich verlassen!");
} }
@@ -109,12 +141,11 @@ public class BorderModule implements Module, Listener {
} }
/** /**
* Holt den zentralen Spawnpunkt aus der Config (Pfad: spawn.world, spawn.x, etc.) * Holt den Spawnpunkt aus der Config (Pfad: spawn.world, spawn.x, etc.)
*/ */
private Location getMainSpawnLocation() { private Location getMainSpawnLocation() {
FileConfiguration config = NexusLobby.getInstance().getConfig(); FileConfiguration config = NexusLobby.getInstance().getConfig();
String worldName = config.getString("spawn.world"); String worldName = config.getString("spawn.world");
if (worldName != null) { if (worldName != null) {
World w = Bukkit.getWorld(worldName); World w = Bukkit.getWorld(worldName);
if (w != null) { if (w != null) {
@@ -126,7 +157,6 @@ public class BorderModule implements Module, Listener {
(float) config.getDouble("spawn.pitch")); (float) config.getDouble("spawn.pitch"));
} }
} }
// Fallback falls kein Spawn gesetzt ist
return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0).getSpawnLocation(); return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0).getSpawnLocation();
} }
} }

View File

@@ -14,7 +14,15 @@ import java.util.Random;
public class ChickenRain { public class ChickenRain {
// FIX: Verhindert, dass ein Spieler den Regen mehrfach gleichzeitig starten kann.
// Ohne diese Prüfung konnten beliebig viele parallele Tasks gestartet werden,
// was zu hunderten gespawnten Entities in Sekunden führte.
private static final java.util.Set<java.util.UUID> activeRains =
java.util.Collections.synchronizedSet(new java.util.HashSet<>());
public static void start(Player player) { public static void start(Player player) {
if (activeRains.contains(player.getUniqueId())) return; // bereits aktiv
activeRains.add(player.getUniqueId());
new BukkitRunnable() { new BukkitRunnable() {
int ticks = 0; int ticks = 0;
final Random random = new Random(); final Random random = new Random();
@@ -22,6 +30,7 @@ public class ChickenRain {
@Override @Override
public void run() { public void run() {
if (!player.isOnline() || ticks > 100) { // 100 Ticks = 5 Sekunden if (!player.isOnline() || ticks > 100) { // 100 Ticks = 5 Sekunden
activeRains.remove(player.getUniqueId());
this.cancel(); this.cancel();
return; return;
} }

View File

@@ -13,8 +13,14 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class FreezeRay { public class FreezeRay {
// Auf public gesetzt, damit das GadgetModule im PlayerMoveEvent darauf prüfen kann
public static final Set<UUID> frozenPlayers = new HashSet<>(); // FIX: private statt public Zugriff nur über isFrozen() und unfreeze()
private static final Set<UUID> frozenPlayers = new HashSet<>();
public static boolean isFrozen(UUID uuid) { return frozenPlayers.contains(uuid); }
/** Beim Disconnect aufrufen, damit kein Ghost-Eintrag bleibt. */
public static void unfreeze(UUID uuid) { frozenPlayers.remove(uuid); }
public static void shoot(Player shooter) { public static void shoot(Player shooter) {
Location start = shooter.getEyeLocation(); Location start = shooter.getEyeLocation();

View File

@@ -47,6 +47,8 @@ public class GadgetModule implements Module, Listener {
@Override @Override
public void onEnable() { public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
// FIX: PetManager-Listener korrekt registrieren (war vorher toter Code)
PetManager.register();
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
PetManager.updatePets(); PetManager.updatePets();
@@ -64,7 +66,7 @@ public class GadgetModule implements Module, Listener {
@EventHandler @EventHandler
public void onInteract(PlayerInteractEvent event) { public void onInteract(PlayerInteractEvent event) {
ItemStack item = event.getItem(); ItemStack item = event.getItem();
if (item == null || !item.hasItemMeta()) return; if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return;
String name = item.getItemMeta().getDisplayName(); String name = item.getItemMeta().getDisplayName();
if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) { if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
@@ -83,7 +85,7 @@ public class GadgetModule implements Module, Listener {
@EventHandler @EventHandler
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
if (FreezeRay.frozenPlayers.contains(event.getPlayer().getUniqueId())) { if (FreezeRay.isFrozen(event.getPlayer().getUniqueId())) {
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getZ() != event.getTo().getZ()) { if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getZ() != event.getTo().getZ()) {
event.setTo(event.getFrom().setDirection(event.getTo().getDirection())); event.setTo(event.getFrom().setDirection(event.getTo().getDirection()));
} }
@@ -216,7 +218,11 @@ public class GadgetModule implements Module, Listener {
else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); } else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); }
} else if (title.equals(HAT_TITLE)) { } else if (title.equals(HAT_TITLE)) {
if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) {
HatManager.setHat(player, item.getType(), item.getItemMeta().getDisplayName()); String hatName = item.getType().name();
if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
hatName = item.getItemMeta().getDisplayName();
}
HatManager.setHat(player, item.getType(), hatName);
player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1); player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1);
player.closeInventory(); player.closeInventory();
} }
@@ -273,7 +279,8 @@ public class GadgetModule implements Module, Listener {
public void onFish(PlayerFishEvent event) { public void onFish(PlayerFishEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand(); ItemStack item = player.getInventory().getItemInMainHand();
if (item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) { if (item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().hasDisplayName()
&& item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) {
if (event.getState() == PlayerFishEvent.State.IN_GROUND || event.getState() == PlayerFishEvent.State.REEL_IN || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { if (event.getState() == PlayerFishEvent.State.IN_GROUND || event.getState() == PlayerFishEvent.State.REEL_IN || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) {
if (event.getHook() != null) { if (event.getHook() != null) {
GrapplingHook.pullPlayer(player, event.getHook().getLocation()); GrapplingHook.pullPlayer(player, event.getHook().getLocation());
@@ -290,6 +297,7 @@ public class GadgetModule implements Module, Listener {
activeEffects.remove(player.getUniqueId()); activeEffects.remove(player.getUniqueId());
activeShields.remove(player.getUniqueId()); activeShields.remove(player.getUniqueId());
PetManager.removePet(player); PetManager.removePet(player);
FreezeRay.unfreeze(player.getUniqueId());
HatManager.removeHat(player); HatManager.removeHat(player);
player.getInventory().remove(Material.FISHING_ROD); player.getInventory().remove(Material.FISHING_ROD);
player.getInventory().remove(Material.PACKED_ICE); player.getInventory().remove(Material.PACKED_ICE);
@@ -331,6 +339,8 @@ public class GadgetModule implements Module, Listener {
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
PetManager.unregister();
PetManager.clearAll(); PetManager.clearAll();
activeBalloons.values().forEach(Balloon::remove); activeBalloons.values().forEach(Balloon::remove);
activeBalloons.clear(); activeBalloons.clear();

View File

@@ -22,10 +22,30 @@ public class PetManager implements Listener {
private static final Map<UUID, Entity> activePets = new HashMap<>(); private static final Map<UUID, Entity> activePets = new HashMap<>();
// Singleton-Instanz, damit registerEvents() nur einmal aufgerufen wird
private static PetManager instance;
public PetManager() { public PetManager() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
} }
/**
* Registriert den PetManager als Listener, falls noch nicht geschehen.
* Muss einmalig beim Plugin-Start aufgerufen werden (z.B. aus GadgetModule.onEnable).
*/
public static void register() {
if (instance == null) {
instance = new PetManager();
}
}
public static void unregister() {
if (instance != null) {
org.bukkit.event.HandlerList.unregisterAll(instance);
instance = null;
}
}
/** /**
* Spawnt ein echtes Tier-Entity für den Spieler. * Spawnt ein echtes Tier-Entity für den Spieler.
*/ */

View File

@@ -158,7 +158,7 @@ public class HologramModule implements Module, Listener {
config.save(file); config.save(file);
} catch (IOException e) { } catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!"); NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!");
e.printStackTrace(); NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Hologramme: " + e.getMessage());
} }
} }
@@ -172,6 +172,7 @@ public class HologramModule implements Module, Listener {
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
holograms.values().forEach(NexusHologram::removeAll); holograms.values().forEach(NexusHologram::removeAll);
holograms.clear(); holograms.clear();
} }

View File

@@ -49,6 +49,7 @@ public class IntroModule implements Module, Listener, CommandExecutor {
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
activeIntro.clear(); activeIntro.clear();
} }
@@ -69,7 +70,11 @@ public class IntroModule implements Module, Listener, CommandExecutor {
private void savePoints() { private void savePoints() {
config.set("points", points); config.set("points", points);
try { config.save(configFile); } catch (IOException e) { e.printStackTrace(); } try {
config.save(configFile);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Intro-Config: " + e.getMessage());
}
} }
@EventHandler @EventHandler

View File

@@ -29,6 +29,8 @@ import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -38,8 +40,18 @@ public class MapArtModule implements Module, CommandExecutor {
private File storageFile; private File storageFile;
private FileConfiguration storageConfig; private FileConfiguration storageConfig;
// RAM-Schutz: Cache für bereits geladene Bild-Kacheln // FIX: Unbegrenzter Cache führt bei vielen verschiedenen Bild-URLs zu einem
private final Map<String, BufferedImage> tileCache = new ConcurrentHashMap<>(); // OutOfMemoryError. Stattdessen nutzen wir einen LRU-Cache mit max. 50 Einträgen.
// Älteste Kacheln werden automatisch verdrängt.
private static final int MAX_CACHE_SIZE = 50;
private final Map<String, BufferedImage> tileCache = Collections.synchronizedMap(
new LinkedHashMap<String, BufferedImage>(MAX_CACHE_SIZE + 1, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, BufferedImage> eldest) {
return size() > MAX_CACHE_SIZE;
}
}
);
// Hilfs-Set um parallele Downloads der gleichen URL zu verhindern // Hilfs-Set um parallele Downloads der gleichen URL zu verhindern
private final Map<String, Boolean> loadingShield = new ConcurrentHashMap<>(); private final Map<String, Boolean> loadingShield = new ConcurrentHashMap<>();
@@ -67,7 +79,7 @@ public class MapArtModule implements Module, CommandExecutor {
try { try {
storageFile.createNewFile(); storageFile.createNewFile();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); NexusLobby.getInstance().getLogger().severe("Fehler beim Erstellen der mapart.yml: " + e.getMessage());
} }
} }
storageConfig = YamlConfiguration.loadConfiguration(storageFile); storageConfig = YamlConfiguration.loadConfiguration(storageFile);
@@ -77,7 +89,7 @@ public class MapArtModule implements Module, CommandExecutor {
try { try {
storageConfig.save(storageFile); storageConfig.save(storageFile);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der mapart.yml: " + e.getMessage());
} }
} }

View File

@@ -37,7 +37,11 @@ public class ParkourManager {
private void loadConfig() { private void loadConfig() {
if (!file.exists()) { if (!file.exists()) {
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } try {
file.createNewFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Erstellen der Parkour-Datei: " + e.getMessage());
}
} }
config = YamlConfiguration.loadConfiguration(file); config = YamlConfiguration.loadConfiguration(file);
} }
@@ -183,7 +187,13 @@ public class ParkourManager {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void save() { try { config.save(file); } catch (IOException e) { e.printStackTrace(); } } private void save() {
try {
config.save(file);
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern der Parkour-Config: " + e.getMessage());
}
}
public void clearStats() { public void clearStats() {
config.set("besttimes", null); config.set("besttimes", null);

View File

@@ -37,7 +37,9 @@ public class PlayerInspectModule implements Module, Listener {
} }
@Override @Override
public void onDisable() {} public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
}
@EventHandler @EventHandler
public void onPlayerInteract(PlayerInteractEntityEvent event) { public void onPlayerInteract(PlayerInteractEntityEvent event) {
@@ -94,9 +96,15 @@ public class PlayerInspectModule implements Module, Listener {
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("§8§m-----------------------"); lore.add("§8§m-----------------------");
// LuckPerms Prefix über PlaceholderAPI auslesen // LuckPerms Prefix über PlaceholderAPI auslesen (falls vorhanden)
String prefix = "%luckperms_prefix%"; String prefix = "%luckperms_prefix%";
prefix = PlaceholderAPI.setPlaceholders(target, prefix); if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
try {
prefix = PlaceholderAPI.setPlaceholders(target, prefix);
} catch (NoClassDefFoundError ignored) {
// PlaceholderAPI fehlt zur Laufzeit
}
}
// KORREKTUR: Farbcodes (&) in echte Minecraft-Farben (§) umwandeln // KORREKTUR: Farbcodes (&) in echte Minecraft-Farben (§) umwandeln
prefix = ChatColor.translateAlternateColorCodes('&', prefix); prefix = ChatColor.translateAlternateColorCodes('&', prefix);

View File

@@ -5,12 +5,15 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class PortalCommand implements CommandExecutor { public class PortalCommand implements CommandExecutor, Listener {
private final PortalManager portalManager; private final PortalManager portalManager;
@@ -20,6 +23,17 @@ public class PortalCommand implements CommandExecutor {
public PortalCommand(PortalManager portalManager) { public PortalCommand(PortalManager portalManager) {
this.portalManager = portalManager; this.portalManager = portalManager;
// FIX: Listener registrieren, damit Selektionen beim Verlassen bereinigt werden
de.nexuslobby.NexusLobby.getInstance().getServer().getPluginManager()
.registerEvents(this, de.nexuslobby.NexusLobby.getInstance());
}
// FIX: Selektionen beim Quit entfernen statische Maps wuchsen sonst ewig
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
selection1.remove(uuid);
selection2.remove(uuid);
} }
// Statische Hilfsmethoden für andere Klassen // Statische Hilfsmethoden für andere Klassen

View File

@@ -31,7 +31,12 @@ import java.util.UUID;
import java.util.Set; import java.util.Set;
/** /**
* PortalManager - Verwaltet Portale, Markierungen und den globalen Grenzschutz. * PortalManager - Verwaltet Portale und Wand-Markierungen.
*
* FIX: Die doppelte Border-Logik wurde entfernt. Der BorderModule ist
* der alleinige Verantwortliche für die Lobby-Grenze. Hätte der
* PortalManager die Border ebenfalls geprüft, wären Spieler doppelt
* teleportiert worden und hätten zwei Nachrichten erhalten.
*/ */
public class PortalManager implements Module, Listener { public class PortalManager implements Module, Listener {
@@ -42,11 +47,6 @@ public class PortalManager implements Module, Listener {
private final NamespacedKey wandKey; private final NamespacedKey wandKey;
private BukkitTask particleTask; private BukkitTask particleTask;
// Boundary Cache
private Location borderMin;
private Location borderMax;
private boolean borderEnabled = false;
public PortalManager(NexusLobby plugin) { public PortalManager(NexusLobby plugin) {
this.plugin = plugin; this.plugin = plugin;
this.wandKey = new NamespacedKey(plugin, "nexuslobby_portal_wand"); this.wandKey = new NamespacedKey(plugin, "nexuslobby_portal_wand");
@@ -60,10 +60,8 @@ public class PortalManager implements Module, Listener {
@Override @Override
public void onEnable() { public void onEnable() {
loadPortals(); loadPortals();
loadBorderSettings();
Bukkit.getPluginManager().registerEvents(this, plugin); Bukkit.getPluginManager().registerEvents(this, plugin);
startParticleTask(); startParticleTask();
plugin.getLogger().info("PortalManager vollständig geladen.");
} }
@Override @Override
@@ -76,20 +74,6 @@ public class PortalManager implements Module, Listener {
plugin.getLogger().info("PortalManager deaktiviert."); plugin.getLogger().info("PortalManager deaktiviert.");
} }
public void loadBorderSettings() {
if (plugin.getConfig().contains("border.pos1") && plugin.getConfig().contains("border.pos2")) {
Location p1 = plugin.getConfig().getLocation("border.pos1");
Location p2 = plugin.getConfig().getLocation("border.pos2");
if (p1 != null && p2 != null) {
this.borderMin = getMinLocation(p1, p2);
this.borderMax = getMaxLocation(p1, p2);
this.borderEnabled = true;
}
} else {
this.borderEnabled = false;
}
}
public Set<String> getPortalNames() { public Set<String> getPortalNames() {
return portals.keySet(); return portals.keySet();
} }
@@ -140,7 +124,7 @@ public class PortalManager implements Module, Listener {
Location loc1 = selectionMap.get(uuid)[0]; Location loc1 = selectionMap.get(uuid)[0];
if (loc1 != null) { if (loc1 != null) {
int width = Math.abs(loc1.getBlockX() - clickedLoc.getBlockX()) + 1; int width = Math.abs(loc1.getBlockX() - clickedLoc.getBlockX()) + 1;
int height = Math.abs(loc1.getBlockY() - clickedLoc.getBlockY()) + 1; int height = Math.abs(loc1.getBlockY() - clickedLoc.getBlockY()) + 1;
int length = Math.abs(loc1.getBlockZ() - clickedLoc.getBlockZ()) + 1; int length = Math.abs(loc1.getBlockZ() - clickedLoc.getBlockZ()) + 1;
long volume = (long) width * height * length; long volume = (long) width * height * length;
@@ -148,7 +132,7 @@ public class PortalManager implements Module, Listener {
p.sendMessage("§7§m----------------------------------"); p.sendMessage("§7§m----------------------------------");
if (volume < 500) { if (volume < 500) {
p.sendMessage("§e[Nexus] Kleiner Bereich erkannt (Portal-Größe)"); p.sendMessage("§e[Nexus] Kleiner Bereich erkannt (Portal-Größe)");
p.sendMessage("§fBefehl: §b/portal create <Name> <server|world>"); p.sendMessage("§fBefehl: §b/portal create <n> <server|world>");
} else { } else {
p.sendMessage("§6[Nexus] Großer Bereich erkannt (WorldBorder-Größe)"); p.sendMessage("§6[Nexus] Großer Bereich erkannt (WorldBorder-Größe)");
p.sendMessage("§fBefehl: §a/border square"); p.sendMessage("§fBefehl: §a/border square");
@@ -182,12 +166,9 @@ public class PortalManager implements Module, Listener {
String type = portalConfig.getString("portals." + key + ".type", "WORLD"); String type = portalConfig.getString("portals." + key + ".type", "WORLD");
Portal portal = new Portal(key, type); Portal portal = new Portal(key, type);
if (portalConfig.contains("portals." + key + ".pos1")) { portal.setPos1(loadLocation(portalConfig, "portals." + key + ".pos1"));
portal.setPos1(portalConfig.getLocation("portals." + key + ".pos1")); portal.setPos2(loadLocation(portalConfig, "portals." + key + ".pos2"));
} portal.setReturnSpawn(loadLocation(portalConfig, "portals." + key + ".returnSpawn"));
if (portalConfig.contains("portals." + key + ".pos2")) {
portal.setPos2(portalConfig.getLocation("portals." + key + ".pos2"));
}
portal.setDestination(portalConfig.getString("portals." + key + ".destination", "")); portal.setDestination(portalConfig.getString("portals." + key + ".destination", ""));
try { try {
@@ -196,10 +177,6 @@ public class PortalManager implements Module, Listener {
portal.setParticle(Particle.PORTAL); portal.setParticle(Particle.PORTAL);
} }
if (portalConfig.contains("portals." + key + ".returnSpawn")) {
portal.setReturnSpawn(portalConfig.getLocation("portals." + key + ".returnSpawn"));
}
portals.put(key, portal); portals.put(key, portal);
} }
} }
@@ -211,21 +188,49 @@ public class PortalManager implements Module, Listener {
for (Portal portal : portals.values()) { for (Portal portal : portals.values()) {
String path = "portals." + portal.getName() + "."; String path = "portals." + portal.getName() + ".";
config.set(path + "type", portal.getType()); config.set(path + "type", portal.getType());
config.set(path + "pos1", portal.getPos1()); saveLocation(config, path + "pos1", portal.getPos1());
config.set(path + "pos2", portal.getPos2()); saveLocation(config, path + "pos2", portal.getPos2());
config.set(path + "destination", portal.getDestination()); config.set(path + "destination", portal.getDestination());
config.set(path + "particle", portal.getParticle() != null ? portal.getParticle().name() : "PORTAL"); config.set(path + "particle", portal.getParticle() != null ? portal.getParticle().name() : "PORTAL");
config.set(path + "returnSpawn", portal.getReturnSpawn()); saveLocation(config, path + "returnSpawn", portal.getReturnSpawn());
} }
try { try {
config.save(new java.io.File(plugin.getDataFolder(), "portals.yml")); config.save(new java.io.File(plugin.getDataFolder(), "portals.yml"));
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().severe("Konnte portals.yml nicht speichern!"); plugin.getLogger().severe("Fehler beim Speichern der portals.yml: " + e.getMessage());
e.printStackTrace();
} }
} }
// FIX: Hilfsmethoden für robustes Location-Speichern/Laden.
// Speichert world + x/y/z als einzelne Keys statt als serialisiertes
// Location-Objekt, was nach Neustarts zu null führen kann.
private void saveLocation(YamlConfiguration config, String path, Location loc) {
if (loc == null || loc.getWorld() == null) return;
config.set(path + ".world", loc.getWorld().getName());
config.set(path + ".x", loc.getX());
config.set(path + ".y", loc.getY());
config.set(path + ".z", loc.getZ());
config.set(path + ".yaw", (double) loc.getYaw());
config.set(path + ".pitch", (double) loc.getPitch());
}
private Location loadLocation(YamlConfiguration config, String path) {
if (!config.contains(path + ".world")) return null;
String worldName = config.getString(path + ".world");
World world = Bukkit.getWorld(worldName);
if (world == null) {
plugin.getLogger().warning("Welt '" + worldName + "' nicht gefunden für Pfad: " + path);
return null;
}
return new Location(world,
config.getDouble(path + ".x"),
config.getDouble(path + ".y"),
config.getDouble(path + ".z"),
(float) config.getDouble(path + ".yaw"),
(float) config.getDouble(path + ".pitch"));
}
// --- CRUD --- // --- CRUD ---
public boolean createPortal(String name, String type, Player creator) { public boolean createPortal(String name, String type, Player creator) {
Location[] sel = selectionMap.getOrDefault(creator.getUniqueId(), new Location[2]); Location[] sel = selectionMap.getOrDefault(creator.getUniqueId(), new Location[2]);
@@ -288,7 +293,7 @@ public class PortalManager implements Module, Listener {
return true; return true;
} }
// --- Movement / Teleport / Boundary Logic --- // --- Movement / Teleport Logic ---
@org.bukkit.event.EventHandler @org.bukkit.event.EventHandler
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
if (event.getFrom().getX() == event.getTo().getX() && if (event.getFrom().getX() == event.getTo().getX() &&
@@ -300,20 +305,7 @@ public class PortalManager implements Module, Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
Location loc = event.getTo(); Location loc = event.getTo();
// 1. Grenzschutz (Boundary Protection) // Portal-Logik
if (borderEnabled && !player.hasPermission("nexuslobby.border.bypass")) {
if (!isWithinBorder(loc)) {
Location spawn = getMainSpawnLocation();
if (spawn != null) {
player.teleport(spawn);
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f);
player.sendMessage("§c§lHEY! §7Du hast den erlaubten Bereich verlassen.");
}
return;
}
}
// 2. Portal Logik
for (Portal portal : portals.values()) { for (Portal portal : portals.values()) {
if (portal.getPos1() == null || portal.getPos2() == null) continue; if (portal.getPos1() == null || portal.getPos2() == null) continue;
if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue; if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue;
@@ -329,14 +321,6 @@ public class PortalManager implements Module, Listener {
} }
} }
private boolean isWithinBorder(Location loc) {
if (borderMin == null || borderMax == null) return true;
if (!loc.getWorld().equals(borderMin.getWorld())) return true;
return loc.getX() >= borderMin.getX() && loc.getX() <= borderMax.getX() &&
loc.getY() >= borderMin.getY() && loc.getY() <= borderMax.getY() &&
loc.getZ() >= borderMin.getZ() && loc.getZ() <= borderMax.getZ();
}
private void executeTeleport(Player player, Portal portal) { private void executeTeleport(Player player, Portal portal) {
if ("SERVER".equalsIgnoreCase(portal.getType())) { if ("SERVER".equalsIgnoreCase(portal.getType())) {
String serverName = portal.getDestination(); String serverName = portal.getDestination();
@@ -345,17 +329,17 @@ public class PortalManager implements Module, Listener {
Location loc = portal.getReturnSpawn(); Location loc = portal.getReturnSpawn();
if (loc == null) { if (loc == null) {
Location pos2 = portal.getPos2(); Location pos2 = portal.getPos2();
loc = player.getLocation(); loc = player.getLocation().clone();
if (pos2 != null) { if (pos2 != null) {
double dx = loc.getX() - pos2.getX(); double dx = loc.getX() - pos2.getX();
double dz = loc.getZ() - pos2.getZ(); double dz = loc.getZ() - pos2.getZ();
double length = Math.sqrt(dx*dx + dz*dz); double length = Math.sqrt(dx * dx + dz * dz);
if (length == 0) length = 1; if (length == 0) length = 1;
dx = (dx / length) * 2; dx = (dx / length) * 2;
dz = (dz / length) * 2; dz = (dz / length) * 2;
loc.add(dx, 0, dz); loc.add(dx, 0, dz);
} else { } else {
loc.add(0,0,2); loc.add(0, 0, 2);
} }
} }
player.teleport(loc); player.teleport(loc);
@@ -386,7 +370,7 @@ public class PortalManager implements Module, Listener {
double x = Double.parseDouble(parts[1]); double x = Double.parseDouble(parts[1]);
double y = Double.parseDouble(parts[2]); double y = Double.parseDouble(parts[2]);
double z = Double.parseDouble(parts[3]); double z = Double.parseDouble(parts[3]);
float yaw = parts.length >= 6 ? Float.parseFloat(parts[4]) : 0f; float yaw = parts.length >= 5 ? Float.parseFloat(parts[4]) : 0f;
float pitch = parts.length >= 6 ? Float.parseFloat(parts[5]) : 0f; float pitch = parts.length >= 6 ? Float.parseFloat(parts[5]) : 0f;
player.teleport(new Location(world, x, y, z, yaw, pitch)); player.teleport(new Location(world, x, y, z, yaw, pitch));
player.sendMessage("§aDu wurdest teleportiert!"); player.sendMessage("§aDu wurdest teleportiert!");
@@ -402,7 +386,7 @@ public class PortalManager implements Module, Listener {
out.writeUTF(serverName); out.writeUTF(serverName);
player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); plugin.getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + e.getMessage());
} }
} }
@@ -454,6 +438,7 @@ public class PortalManager implements Module, Listener {
Location min = getMinLocation(portal.getPos1(), portal.getPos2()); Location min = getMinLocation(portal.getPos1(), portal.getPos2());
Location max = getMaxLocation(portal.getPos1(), portal.getPos2()); Location max = getMaxLocation(portal.getPos1(), portal.getPos2());
World world = portal.getPos1().getWorld(); World world = portal.getPos1().getWorld();
if (world == null) return;
for (int i = 0; i < 15; i++) { for (int i = 0; i < 15; i++) {
double x = min.getX() + Math.random() * (max.getX() - min.getX() + 1); double x = min.getX() + Math.random() * (max.getX() - min.getX() + 1);
double y = min.getY() + Math.random() * (max.getY() - min.getY() + 1); double y = min.getY() + Math.random() * (max.getY() - min.getY() + 1);

View File

@@ -115,5 +115,7 @@ public class SecurityModule implements Module, Listener {
} }
@Override @Override
public void onDisable() {} public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
}
} }

View File

@@ -5,6 +5,7 @@ import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.scheduler.BukkitTask;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -13,6 +14,8 @@ import java.util.concurrent.CompletableFuture;
public class ServerChecker { public class ServerChecker {
private static BukkitTask globalCheckerTask = null;
/** /**
* Prüft asynchron, ob ein Server unter der angegebenen IP und Port erreichbar ist. * Prüft asynchron, ob ein Server unter der angegebenen IP und Port erreichbar ist.
*/ */
@@ -32,8 +35,11 @@ public class ServerChecker {
* Startet den Scheduler, der alle ArmorStands in allen Welten regelmäßig prüft. * Startet den Scheduler, der alle ArmorStands in allen Welten regelmäßig prüft.
*/ */
public static void startGlobalChecker() { public static void startGlobalChecker() {
// Cancel alten Task falls vorhanden
stopGlobalChecker();
// WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen // WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { globalCheckerTask = Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
// Das Abrufen der Entities muss auf dem Main-Thread passieren // Das Abrufen der Entities muss auf dem Main-Thread passieren
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
@@ -43,6 +49,16 @@ public class ServerChecker {
}, 100L, 200L); }, 100L, 200L);
} }
/**
* Stoppt den Server-Checker Task
*/
public static void stopGlobalChecker() {
if (globalCheckerTask != null && !globalCheckerTask.isCancelled()) {
globalCheckerTask.cancel();
globalCheckerTask = null;
}
}
/** /**
* Analysiert die Tags eines ArmorStands und aktualisiert den Namen basierend auf dem Serverstatus. * Analysiert die Tags eines ArmorStands und aktualisiert den Namen basierend auf dem Serverstatus.
*/ */

View File

@@ -111,7 +111,7 @@ public class ServerSwitcherGUI {
private static void connectToServer(Player player, String serverName, Plugin plugin) { private static void connectToServer(Player player, String serverName, Plugin plugin) {
try { try {
if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) { if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) {
player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)."); player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_not_registered"));
return; return;
} }
@@ -120,13 +120,13 @@ public class ServerSwitcherGUI {
out.writeUTF("Connect"); out.writeUTF("Connect");
out.writeUTF(serverName); out.writeUTF(serverName);
player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray());
player.sendMessage(ChatColor.YELLOW + "Verbinde zu: " + ChatColor.WHITE + serverName); player.sendMessage(de.nexuslobby.utils.LangManager.get("connecting_to_server").replace("{server}", serverName));
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); NexusLobby.getInstance().getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + ex.getMessage());
player.sendMessage(ChatColor.RED + "Fehler beim Verbinden zum Server."); player.sendMessage(de.nexuslobby.utils.LangManager.get("server_connect_error"));
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
// Schutz gegen unerwartete RuntimeExceptions bei plugin messaging // Schutz gegen unerwartete RuntimeExceptions bei plugin messaging
player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich."); player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_connect_error"));
plugin.getLogger().warning("Fehler beim Senden der Plugin-Message: " + ex.getMessage()); plugin.getLogger().warning("Fehler beim Senden der Plugin-Message: " + ex.getMessage());
} }
} }

View File

@@ -4,6 +4,9 @@ import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -19,7 +22,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ServerSwitcherListener implements Listener { public class ServerSwitcherListener implements Listener, CommandExecutor {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent e) { public void onPlayerJoin(PlayerJoinEvent e) {
@@ -51,6 +54,23 @@ public class ServerSwitcherListener implements Listener {
} }
} }
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player"));
return true;
}
Player player = (Player) sender;
if (!player.hasPermission("nexuslobby.serverswitcher")) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
return true;
}
openServerGUI(player);
return true;
}
@EventHandler @EventHandler
public void onCompassClick(PlayerInteractEvent e) { public void onCompassClick(PlayerInteractEvent e) {
Player p = e.getPlayer(); Player p = e.getPlayer();

View File

@@ -61,6 +61,7 @@ public class LobbySettingsModule implements Module, Listener {
} }
// Hilfsmethode zur sicheren Anwendung von GameRules in 1.21 // Hilfsmethode zur sicheren Anwendung von GameRules in 1.21
@SuppressWarnings("unchecked")
private void updateGameRule(World world, String ruleName, Object value) { private void updateGameRule(World world, String ruleName, Object value) {
GameRule<?> rule = GameRule.getByName(ruleName); GameRule<?> rule = GameRule.getByName(ruleName);
if (rule == null) return; if (rule == null) return;
@@ -117,6 +118,7 @@ public class LobbySettingsModule implements Module, Listener {
Material mat = isBool ? ((Boolean)value ? Material.LIME_DYE : Material.GRAY_DYE) : Material.BOOK; Material mat = isBool ? ((Boolean)value ? Material.LIME_DYE : Material.GRAY_DYE) : Material.BOOK;
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName("§e" + key); meta.setDisplayName("§e" + key);
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("§7Aktueller Wert: §f" + value); lore.add("§7Aktueller Wert: §f" + value);
@@ -160,13 +162,17 @@ public class LobbySettingsModule implements Module, Listener {
private void handleSubClick(InventoryClickEvent event) { private void handleSubClick(InventoryClickEvent event) {
Player p = (Player) event.getWhoClicked(); Player p = (Player) event.getWhoClicked();
if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) return; ItemStack current = event.getCurrentItem();
if (event.getCurrentItem().getType() == Material.BARRIER) { if (current == null || current.getType() == Material.AIR) return;
if (current.getType() == Material.BARRIER) {
openMainMenu(p); openMainMenu(p);
return; return;
} }
ItemMeta meta = current.getItemMeta();
String key = event.getCurrentItem().getItemMeta().getDisplayName().substring(2); if (meta == null || !meta.hasDisplayName()) return;
String displayName = meta.getDisplayName();
if (displayName.length() < 3) return;
String key = displayName.substring(2);
String path = settingsConfig.contains("gamerules." + key) ? "gamerules." + key : key; String path = settingsConfig.contains("gamerules." + key) ? "gamerules." + key : key;
Object value = settingsConfig.get(path); Object value = settingsConfig.get(path);
String category = event.getView().getTitle().replace(subTitlePrefix, ""); String category = event.getView().getTitle().replace(subTitlePrefix, "");
@@ -203,12 +209,17 @@ public class LobbySettingsModule implements Module, Listener {
} }
private void saveSettings() { private void saveSettings() {
try { settingsConfig.save(configFile); } catch (IOException e) { e.printStackTrace(); } try {
settingsConfig.save(configFile);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der settings.yml: " + e.getMessage());
}
} }
private ItemStack createItem(Material mat, String name, String lore) { private ItemStack createItem(Material mat, String name, String lore) {
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name); meta.setDisplayName(name);
if (!lore.isEmpty()) { if (!lore.isEmpty()) {
List<String> lores = new ArrayList<>(); List<String> lores = new ArrayList<>();
@@ -220,5 +231,8 @@ public class LobbySettingsModule implements Module, Listener {
} }
@Override @Override
public void onDisable() { saveSettings(); } public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
saveSettings();
}
} }

View File

@@ -47,6 +47,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CONTROL); plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CONTROL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CHAT); plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CHAT);
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_CONTROL); plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_CONTROL);
@@ -84,7 +85,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List
} }
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Bukkit.getLogger().severe("Fehler beim Abrufen des Conversation-Managers: " + e.getMessage());
} }
} }

View File

@@ -1,6 +1,7 @@
package de.nexuslobby.utils; package de.nexuslobby.utils;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@@ -23,7 +24,13 @@ public class ConfigUpdater {
FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile); FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile);
List<String> newFileContent = new ArrayList<>(); List<String> newFileContent = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(plugin.getResource(fileName), StandardCharsets.UTF_8))) { InputStream resourceStream = plugin.getResource(fileName);
if (resourceStream == null) {
plugin.getLogger().severe("Config-Update fehlgeschlagen: Resource fehlt: " + fileName);
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
String trimmed = line.trim(); String trimmed = line.trim();
@@ -52,7 +59,7 @@ public class ConfigUpdater {
} }
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Bukkit.getLogger().severe("Fehler beim Config-Update: " + e.getMessage());
} }
// 3. Datei sauber speichern // 3. Datei sauber speichern

View File

@@ -0,0 +1,155 @@
package de.nexuslobby.utils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
/**
* Validiert Config-Werte und setzt Default-Werte wenn nötig
*/
public class ConfigValidator {
private final JavaPlugin plugin;
private final FileConfiguration config;
private final List<String> warnings = new ArrayList<>();
private boolean modified = false;
public ConfigValidator(JavaPlugin plugin, FileConfiguration config) {
this.plugin = plugin;
this.config = config;
}
/**
* Validiert alle Config-Werte und gibt Warnings aus
*/
public void validate() {
validateSpawn();
validateLobbySettings();
validateTablist();
validateBall();
validateModules();
// Warnings ausgeben
if (!warnings.isEmpty()) {
plugin.getLogger().warning("=== Config-Validierung ===");
warnings.forEach(msg -> plugin.getLogger().warning(" " + msg));
plugin.getLogger().warning("=========================");
}
// Config speichern wenn Änderungen vorgenommen wurden
if (modified) {
plugin.saveConfig();
plugin.getLogger().info("Config wurde mit Default-Werten aktualisiert.");
}
}
private void validateSpawn() {
ensureDefault("spawn.world", "world");
ensureDefault("spawn.x", 0.5);
ensureDefault("spawn.y", 64.0);
ensureDefault("spawn.z", 0.5);
ensureDefault("spawn.yaw", 0.0);
ensureDefault("spawn.pitch", 0.0);
// Validiere Y-Koordinate
double y = config.getDouble("spawn.y", 64.0);
if (y < -64 || y > 320) {
warnings.add("spawn.y (" + y + ") außerhalb des gültigen Bereichs (-64 bis 320)");
}
}
private void validateLobbySettings() {
ensureDefault("lobby.allow-fly", false);
ensureDefault("lobby.pvp-enabled", false);
ensureDefault("lobby.build-enabled", false);
ensureDefault("lobby.default-gamemode", "Adventure");
ensureDefault("lobby.clear-inventory-on-join", true);
// Validiere GameMode
String gamemode = config.getString("lobby.default-gamemode", "Adventure");
if (!isValidGameMode(gamemode)) {
warnings.add("lobby.default-gamemode '" + gamemode + "' ist ungültig. Nutze: Survival, Creative, Adventure oder Spectator");
}
}
private void validateTablist() {
ensureDefault("tablist.enabled", true);
ensureDefault("tablist.header", "&6Willkommen auf &eNexusLobby");
ensureDefault("tablist.footer", "&7Viel Spaß!");
ensureDefault("tablist.refresh-interval", 40);
// Validiere Refresh-Interval
int interval = config.getInt("tablist.refresh-interval", 40);
if (interval < 1) {
warnings.add("tablist.refresh-interval (" + interval + ") ist zu klein. Minimum ist 1 Tick.");
config.set("tablist.refresh-interval", 1);
modified = true;
} else if (interval > 200) {
warnings.add("tablist.refresh-interval (" + interval + ") ist sehr hoch. Empfohlen: 20-100 Ticks.");
}
}
private void validateBall() {
ensureDefault("ball.respawn_delay", 60);
// Validiere Respawn-Delay
long delay = config.getLong("ball.respawn_delay", 60);
if (delay < 5) {
warnings.add("ball.respawn_delay (" + delay + "s) ist zu kurz. Minimum empfohlen: 5 Sekunden.");
} else if (delay > 600) {
warnings.add("ball.respawn_delay (" + delay + "s) ist sehr lang. Empfohlen: 30-120 Sekunden.");
}
// Prüfe ob Spawn gesetzt ist
if (!config.contains("ball.spawn")) {
warnings.add("ball.spawn nicht gesetzt. Nutze /nexuslobby ball setspawn");
}
}
private void validateModules() {
// Worldborder
if (config.contains("worldborder.enabled")) {
ensureDefault("worldborder.type", "SQUARE");
ensureDefault("worldborder.radius", 50.0);
double radius = config.getDouble("worldborder.radius", 50.0);
if (radius < 10) {
warnings.add("worldborder.radius (" + radius + ") ist sehr klein. Minimum empfohlen: 10 Blöcke.");
} else if (radius > 10000) {
warnings.add("worldborder.radius (" + radius + ") ist extrem groß. Performance-Warnung!");
}
String type = config.getString("worldborder.type", "SQUARE");
if (!type.equalsIgnoreCase("SQUARE") && !type.equalsIgnoreCase("CIRCLE")) {
warnings.add("worldborder.type '" + type + "' ist ungültig. Nutze: SQUARE oder CIRCLE");
}
}
// Items Modul
if (config.contains("items.lobby-tools")) {
// Prüfe ob Slot-Nummern gültig sind (0-8)
for (String toolKey : config.getConfigurationSection("items.lobby-tools").getKeys(false)) {
int slot = config.getInt("items.lobby-tools." + toolKey + ".slot", -1);
if (slot < 0 || slot > 8) {
warnings.add("items.lobby-tools." + toolKey + ".slot (" + slot + ") ist ungültig (0-8)");
}
}
}
}
private void ensureDefault(String path, Object defaultValue) {
if (!config.contains(path)) {
config.set(path, defaultValue);
modified = true;
}
}
private boolean isValidGameMode(String mode) {
return mode.equalsIgnoreCase("Survival") ||
mode.equalsIgnoreCase("Creative") ||
mode.equalsIgnoreCase("Adventure") ||
mode.equalsIgnoreCase("Spectator");
}
}

View File

@@ -0,0 +1,32 @@
package de.nexuslobby.utils;
import org.bukkit.plugin.java.JavaPlugin;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
public class LangManager {
private static Map<String, Map<String, String>> langMap;
private static String currentLang = "de";
public static void load(JavaPlugin plugin) {
Yaml yaml = new Yaml();
try (InputStream in = plugin.getResource("lang.yml")) {
langMap = yaml.load(in);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setLanguage(String lang) {
currentLang = lang;
}
public static String get(String key) {
if (langMap == null) return key;
Map<String, String> translations = langMap.get(key);
if (translations == null) return key;
return translations.getOrDefault(currentLang, key);
}
}

View File

@@ -24,13 +24,22 @@ public class PlayerHider implements Listener {
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return; if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return;
Material material = Material.valueOf(NexusLobby.getInstance().getConfig().getString("hider.item", "REDSTONE")); var config = NexusLobby.getInstance().getConfig();
int slot = NexusLobby.getInstance().getConfig().getInt("hider.slot", 8); Material material;
try {
material = Material.valueOf(config.getString("hider.item", "REDSTONE"));
} catch (IllegalArgumentException e) {
material = Material.REDSTONE;
}
int slot = config.getInt("hider.slot", 8);
ItemStack item = new ItemStack(material); ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
meta.setDisplayName(NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§")); if (meta != null) {
item.setItemMeta(meta); String allMsg = colorize(config.getString("hider.messages.all", "&aAlle Spieler"));
meta.setDisplayName(allMsg);
item.setItemMeta(meta);
}
event.getPlayer().getInventory().setItem(slot, item); event.getPlayer().getInventory().setItem(slot, item);
} }
@@ -39,32 +48,40 @@ public class PlayerHider implements Listener {
public void onInteract(PlayerInteractEvent event) { public void onInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.getItem() == null) return; if (event.getItem() == null) return;
if (!event.getItem().hasItemMeta()) return;
if (event.getItem().getItemMeta() == null) return;
if (!event.getItem().getItemMeta().hasDisplayName()) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
String allMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§"); var config = NexusLobby.getInstance().getConfig();
String noneMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.none").replace("&", "§"); String allMsg = de.nexuslobby.utils.LangManager.get("hider_all");
String noneMsg = de.nexuslobby.utils.LangManager.get("hider_none");
if (event.getItem().getItemMeta().getDisplayName().equals(allMsg)) { if (event.getItem().getItemMeta().getDisplayName().equals(allMsg)) {
// Verstecken // Verstecken
hidden.add(player.getUniqueId()); hidden.add(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target); for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target);
updateItem(player, noneMsg); updateItem(player, noneMsg);
player.sendMessage(noneMsg); player.sendMessage(noneMsg);
} else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) { } else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) {
// Zeigen // Zeigen
hidden.remove(player.getUniqueId()); hidden.remove(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target); for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target);
updateItem(player, allMsg); updateItem(player, allMsg);
player.sendMessage(allMsg); player.sendMessage(allMsg);
} }
} }
private void updateItem(Player player, String name) { private void updateItem(Player player, String name) {
ItemStack item = player.getInventory().getItemInHand(); ItemStack item = player.getInventory().getItemInMainHand();
if (item == null || !item.hasItemMeta()) return;
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.setDisplayName(name); meta.setDisplayName(name);
item.setItemMeta(meta); item.setItemMeta(meta);
} }
private String colorize(String s) {
return s == null ? "" : s.replace("&", "§");
}
} }

View File

@@ -1,18 +1,24 @@
package de.nexuslobby.utils; package de.nexuslobby.utils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.Scanner; import java.nio.charset.StandardCharsets;
import java.util.function.Consumer; import java.util.function.Consumer;
public class UpdateChecker { public class UpdateChecker {
private final NexusLobby plugin; private final NexusLobby plugin;
// URL zur Gitea API für das neueste Release private static final String API_URL = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest";
private final String url = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest"; private static final int TIMEOUT_MS = 5000;
public UpdateChecker(NexusLobby plugin) { public UpdateChecker(NexusLobby plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -20,23 +26,60 @@ public class UpdateChecker {
public void getVersion(final Consumer<String> consumer) { public void getVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream inputStream = new URL(url).openStream(); Scanner scanner = new Scanner(inputStream)) { HttpURLConnection connection = null;
StringBuilder response = new StringBuilder(); try {
while (scanner.hasNextLine()) { URL url = new URL(API_URL);
response.append(scanner.nextLine()); connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(TIMEOUT_MS);
connection.setReadTimeout(TIMEOUT_MS);
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "NexusLobby-UpdateChecker");
int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
plugin.getLogger().warning("Update-Check fehlgeschlagen: HTTP " + responseCode);
return;
} }
String content = response.toString(); try (BufferedReader reader = new BufferedReader(
// Einfaches Parsing des JSON "tag_name" Feldes new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
if (content.contains("\"tag_name\":")) {
String version = content.split("\"tag_name\":\"")[1].split("\"")[0]; StringBuilder response = new StringBuilder();
// Entferne ein eventuelles 'v' Präfix (z.B. v1.0.0 -> 1.0.0) String line;
version = version.replace("v", ""); while ((line = reader.readLine()) != null) {
consumer.accept(version); response.append(line);
}
parseAndNotify(response.toString(), consumer);
}
} catch (IOException e) {
plugin.getLogger().warning("Update-Check fehlgeschlagen: " + e.getMessage());
} finally {
if (connection != null) {
connection.disconnect();
} }
} catch (IOException exception) {
this.plugin.getLogger().warning("Update-Check fehlgeschlagen: " + exception.getMessage());
} }
}); });
} }
private void parseAndNotify(String jsonResponse, Consumer<String> consumer) {
try {
JsonObject json = JsonParser.parseString(jsonResponse).getAsJsonObject();
if (json.has("tag_name")) {
String version = json.get("tag_name").getAsString();
// Entferne 'v' Präfix falls vorhanden (z.B. v1.0.0 -> 1.0.0)
if (version.startsWith("v")) {
version = version.substring(1);
}
consumer.accept(version);
} else {
plugin.getLogger().warning("Update-Check: 'tag_name' nicht in API-Response gefunden");
}
} catch (JsonSyntaxException e) {
plugin.getLogger().warning("Update-Check: Ungültiges JSON-Format - " + e.getMessage());
}
}
} }

View File

@@ -19,9 +19,9 @@ public class VoidProtection implements Listener {
if (NexusLobby.getInstance().getConfig().getBoolean("void_protection.teleport_to_spawn", true)) { if (NexusLobby.getInstance().getConfig().getBoolean("void_protection.teleport_to_spawn", true)) {
Location spawn = player.getWorld().getSpawnLocation(); Location spawn = player.getWorld().getSpawnLocation();
player.teleport(spawn); player.teleport(spawn);
String msg = NexusLobby.getInstance().getConfig().getString("void_protection.message"); String msg = de.nexuslobby.utils.LangManager.get("void_protection_message");
if (msg != null && !msg.isEmpty()) { if (msg != null && !msg.isEmpty()) {
player.sendMessage(msg.replace("&", "§")); player.sendMessage(msg);
} }
} }
} }

View File

@@ -1,5 +1,13 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# -----------------------------------------------------
# ArmorStandTools Configuration # ArmorStandTools Configuration
# ----------------------------- # -----------------------------------------------------
# Nachrichten # Nachrichten
prefix: "§8[§6ArmorStand§8] §7" prefix: "§8[§6ArmorStand§8] §7"

View File

@@ -1,86 +1,252 @@
# ========================== # _ __ __ __ __
# NexusLobby Konfiguration # / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# ========================== # / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# =============================================================
# NEXUSLOBBY - HAUPTKONFIGURATION
# =============================================================
#
# Diese Konfigurationsdatei steuert alle Aspekte deiner Lobby.
# Farben werden mit & formatiert (z.B. &a = grün, &c = rot, &e = gelb)
# Vollständige Farbcode-Liste: https://minecraft.fandom.com/wiki/Formatting_codes
# --- Spawn Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
# SPRACHE
# ══════════════════════════════════════════════════════════════════════════════
# Spracheinstellung für alle Texte und Nachrichten im Plugin
# Verfügbare Optionen: de (Deutsch), en (Englisch), fr (Französisch)
language: de
# ══════════════════════════════════════════════════════════════════════════════
# SPAWN EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Definiert den Spawn-Punkt der Lobby, zu dem Spieler beim Join teleportiert werden
# Tipp: Nutze den Befehl /nexus setspawn um diese Werte automatisch zu setzen
spawn: spawn:
world: "world" # Name der Standardwelt # Name der Welt, in der sich der Spawn befindet
x: 0.5 # X-Koordinate des Spawns world: "world"
y: 64.0 # Y-Koordinate des Spawns
z: 0.5 # Z-Koordinate des Spawns
yaw: 0.0 # Blickrichtung
pitch: 0.0 # Blickrichtung
# X-Koordinate des Spawn-Punktes (Ost-West)
x: 0.5
# Y-Koordinate des Spawn-Punktes (Höhe)
y: 64.0
# Z-Koordinate des Spawn-Punktes (Nord-Süd)
z: 0.5
# Horizontale Blickrichtung beim Spawn (0-360 Grad)
# 0 = Süden, 90 = Westen, 180 = Norden, 270 = Osten
yaw: 0.0
# Vertikale Blickrichtung beim Spawn (-90 bis 90 Grad)
# -90 = Direkt nach oben, 0 = Geradeaus, 90 = Direkt nach unten
pitch: 0.0
# ══════════════════════════════════════════════════════════════════════════════
# WELTGRENZE (WORLD BORDER)
# ══════════════════════════════════════════════════════════════════════════════
# Erstellt eine unsichtbare Barriere um die Lobby herum
worldborder: worldborder:
# Aktiviert die Weltgrenze (true = an, false = aus)
enabled: true enabled: true
type: "SQUARE" # oder "CIRCLE"
radius: 50.0
center:
pos1:
pos2:
# --- Lobby Einstellungen --- # Form der Weltgrenze
# Optionen: "SQUARE" (Quadrat), "CIRCLE" (Kreis)
type: "SQUARE"
# Radius der Weltgrenze in Blöcken
# Bei SQUARE: Kantenlänge = radius * 2
# Bei CIRCLE: Durchmesser = radius * 2
radius: 50.0
# Zentrum der Weltgrenze (optional, kann leer bleiben)
# Wenn nicht gesetzt, wird der Spawn-Punkt als Zentrum verwendet
# center wird automatisch über /border circle gesetzt
# Alternative: Definiere Eckpunkte (für rechteckige Border)
# Format: x,y,z
# pos1 wird automatisch über /border square gesetzt
# pos2 wird automatisch über /border square gesetzt
# ══════════════════════════════════════════════════════════════════════════════
# LOBBY EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Grundlegende Verhaltensregeln und Einstellungen für die Lobby
lobby: lobby:
allow-fly: false # Spieler dürfen fliegen # Erlaubt Spielern das Fliegen in der Lobby
pvp-enabled: false # PvP in der Lobby # true = Spieler können fliegen, false = Fliegen ist deaktiviert
build-enabled: false # Bau im Lobby-Bereich allow-fly: false
# Aktiviert PvP (Player vs Player) in der Lobby
# true = Spieler können sich gegenseitig angreifen, false = kein PvP
pvp-enabled: false
# Erlaubt Spielern das Platzieren und Abbauen von Blöcken
# true = Bauen erlaubt, false = Bauen verboten (außer für Berechtigte)
build-enabled: false
# Standard-Spielmodus für alle Spieler in der Lobby
# Optionen: Survival, Creative, Adventure, Spectator
default-gamemode: Adventure default-gamemode: Adventure
# Leert das Inventar von Spielern beim Betreten der Lobby
# true = Inventar wird geleert, false = Inventar bleibt erhalten
# Empfohlen: true für ein sauberes Lobby-Erlebnis
clear-inventory-on-join: true clear-inventory-on-join: true
# Mapping für den Server-Status-Ping der ArmorStands # ══════════════════════════════════════════════════════════════════════════════
# Der Name (z.B. survival) muss exakt dem Bungee-Servernamen entsprechen # SCOREBOARD EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Steuert das Scoreboard (rechte Seitenleiste) in der Lobby
# Standardmäßig sichtbar? Spieler können es per Befehl ein-/ausblenden
# true = Scoreboard wird beim Join angezeigt, false = standardmäßig ausgeblendet
scoreboard-default-visible: true
# ══════════════════════════════════════════════════════════════════════════════
# SERVER-STATUS PING (FÜR ARMORSTANDS)
# ══════════════════════════════════════════════════════════════════════════════
# Mapping für den Server-Status-Ping der ArmorStands in der Lobby
# Diese Einstellungen ermöglichen es, den Status anderer Server zu überprüfen
# WICHTIG: Der Name (z.B. "survival") muss EXAKT dem BungeeCord-Servernamen entsprechen
servers: servers:
# Erster Server: Survival
survival: survival:
# IP-Adresse des Survival-Servers
# 127.0.0.1 = Localhost (Server läuft auf derselben Maschine)
ip: "127.0.0.1" ip: "127.0.0.1"
# Port des Survival-Servers
port: 25566 port: 25566
# Zweiter Server: Skyblock
skyblock: skyblock:
# IP-Adresse des Skyblock-Servers
ip: "127.0.0.1" ip: "127.0.0.1"
# Port des Skyblock-Servers
port: 25567 port: 25567
# --- Tablist Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
# TABLISTE (TAB-LISTE) EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Anpassung der Player-Liste (TAB-Taste)
tablist: tablist:
# Aktiviert die angepasste Tabliste
# true = Custom Tablist wird verwendet, false = Standard Minecraft Tablist
enabled: true enabled: true
header: "&6Willkommen auf &eNexusLobby"
footer: "&7Viel Spaß!"
refresh-interval: 40 # Ticks
# --- Items Modul Einstellungen --- # Kopfzeile über den Spielernamen
# Unterstützt Farbcodes (&-Codes) und mehrzeiligen Text mit |
header: "&6Willkommen auf &eNexusLobby"
# Fußzeile unter den Spielernamen
# Unterstützt Farbcodes (&-Codes) und mehrzeiligen Text mit |
footer: "&7Viel Spaß!"
# Aktualisierungsintervall in Ticks (20 Ticks = 1 Sekunde)
# Niedrigere Werte = häufigere Updates (mehr Performance-Last)
# Höhere Werte = seltenere Updates (weniger Performance-Last)
refresh-interval: 40
# ══════════════════════════════════════════════════════════════════════════════
# ITEMS MODUL EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration der Items, die Spieler in der Lobby erhalten
items: items:
# Container für alle Lobby-Werkzeuge
lobby-tools: lobby-tools:
# Kompass (Server-Auswahl / Teleporter)
compass: compass:
enabled: true # Aktiviert das Kompass-Item (true = an, false = aus)
displayname: "&eTeleporter"
slot: 4
build-toggle:
enabled: true
displayname: "&aBaumodus"
slot: 0
gadget:
enabled: false enabled: false
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&eTeleporter"
# Slot im Inventar (0-8, wobei 0 ganz links ist und 8 ganz rechts)
slot: 4
# Baumodus-Umschalter (nur für berechtigte Spieler)
build-toggle:
# Aktiviert das Baumodus-Item (true = an, false = aus)
enabled: true
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&aBaumodus"
# Slot im Inventar (0-8)
slot: 0
# Gadget-Menü (Spezialeffekte und Extras)
gadget:
# Aktiviert das Gadget-Item (true = an, false = aus)
enabled: true
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&bGadgets" displayname: "&bGadgets"
# Slot im Inventar (0-8)
slot: 8 slot: 8
# --- Portal Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
# PORTAL EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration für Teleportations-Portale in der Lobby
portals: portals:
# Standard-Partikeleffekt für alle Portale
# Optionen: PORTAL, FLAME, VILLAGER_HAPPY, REDSTONE, ENCHANTMENT_TABLE, etc.
# Vollständige Liste: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html
default-particle: "PORTAL" default-particle: "PORTAL"
portal-cooldown: 40 # Ticks, 2 Sekunden
# Cooldown zwischen Portal-Nutzungen in Ticks (20 Ticks = 1 Sekunde)
# Verhindert Spam und ungewollte Mehrfach-Teleports
portal-cooldown: 40
# Dateiname für die Speicherung der Portal-Positionen
# Wird automatisch im Plugin-Ordner erstellt
save-file: "portals.yml" save-file: "portals.yml"
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# COMPASS MENU # COMPASS MENU (SERVER SWITCHER)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# GUI-Menü das sich öffnet, wenn ein Spieler auf den Kompass klickt
compass: compass:
# Titel des Inventar-Menüs (unterstützt Farbcodes)
title: "&eServer Switcher" title: "&eServer Switcher"
# Größe des Inventars (muss ein Vielfaches von 9 sein)
# Optionen: 9, 18, 27, 36, 45, 54
size: 27 size: 27
# Server-Einträge im Menü
servers: servers:
# Erster Server: PvP
pvp: pvp:
# Anzeigename des Items im Menü (unterstützt Farbcodes)
name: "&cPvP Arena" name: "&cPvP Arena"
# Material des Items (muss ein gültiger Minecraft-Material-Name sein)
# Liste: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html
material: "DIAMOND_SWORD" material: "DIAMOND_SWORD"
# Befehl der beim Klick ausgeführt wird
# Für BungeeCord: "server <servername>"
# Für Teleport: "spawn <name>" oder andere Custom-Befehle
command: "server pvp" command: "server pvp"
# Position des Items im Inventar (0 = oben links)
slot: 11 slot: 11
# Beschreibung des Items (wird beim Darüberfahren angezeigt)
lore: lore:
- "&7Klicke hier um dich" - "&7Klicke hier um dich"
- "&7zum PvP Server zu teleportieren." - "&7zum PvP Server zu teleportieren."
# Zweiter Server: Survival
survival: survival:
name: "&aSurvival" name: "&aSurvival"
material: "GRASS_BLOCK" material: "GRASS_BLOCK"
@@ -89,6 +255,8 @@ compass:
lore: lore:
- "&7Das normale Survival." - "&7Das normale Survival."
- "&7Viel Spaß beim Bauen!" - "&7Viel Spaß beim Bauen!"
# Dritter Server: BuildBattle
buildbattle: buildbattle:
name: "&bBuildBattle" name: "&bBuildBattle"
material: "BEDROCK" material: "BEDROCK"
@@ -97,82 +265,223 @@ compass:
lore: lore:
- "&7Zeige was du kannst!" - "&7Zeige was du kannst!"
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# PLAYER INSPECT (Statistiken per Rechtsklick) # PLAYER INSPECT (STATISTIKEN PER RECHTSKLICK)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Ermöglicht das Anzeigen von Spieler-Statistiken durch Rechtsklick auf einen Spieler
player_inspect: player_inspect:
# Aktiviert die Player-Inspect-Funktion (true = an, false = aus)
enabled: true enabled: true
# Titel des GUI-Fensters das sich öffnet
# Platzhalter: {PLAYER} = Name des angeklickten Spielers
gui_title: "&8Statistiken von &6{PLAYER}" gui_title: "&8Statistiken von &6{PLAYER}"
# --- Suppressor / Global Chat Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
# SUPPRESSOR / GLOBAL CHAT EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Join/Quit-Nachrichten Unterdrückung und BungeeCord-Messaging
suppressor: suppressor:
enabled: true # Aktiviert das Suppressor-System (true = an, false = aus)
suppress-join-quit: true
suppress-duration-ticks: 40 # Zeit, bis Spieler wieder sichtbar
channels:
control: "global:control" # Channel für Join/Quit Suppression
chat: "global:chat" # Channel für globales Chat-Relay
# --- Logging Einstellungen ---
logging:
enable-debug: true # Aktiviert detaillierte Logs für Module
log-file: "logs/plugin.log" # Pfad für das Logfile
# --- Wartungsmodus ---
maintenance:
enabled: false enabled: false
# Unterdrückt Join- und Quit-Nachrichten für neue Spieler temporär
# true = Nachrichten werden unterdrückt, false = normale Anzeige
# Nützlich um Spam zu vermeiden wenn viele Spieler gleichzeitig joinen/leaven
suppress-join-quit: true
# Dauer der Unterdrückung in Ticks (20 Ticks = 1 Sekunde)
# Nach dieser Zeit werden Join/Quit-Nachrichten wieder normal angezeigt
suppress-duration-ticks: 40
# BungeeCord Plugin-Messaging Channels
# Diese Channels werden für die Kommunikation zwischen Servern verwendet
channels:
# Channel für Join/Quit-Suppression-Control
control: "global:control"
# Channel für globales Chat-Relay über alle Server
chat: "global:chat"
# ══════════════════════════════════════════════════════════════════════════════
# LOGGING EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration der Plugin-Logs und Debug-Ausgaben
logging:
# Aktiviert detaillierte Debug-Logs in der Konsole
# true = Sehr ausführliche Logs (nur zur Fehlersuche empfohlen)
# false = Normale Logs
# WARNUNG: Bei true kann die Konsole sehr voll werden!
enable-debug: true
# Pfad zur Log-Datei des Plugins
# Relativ zum Plugin-Ordner
log-file: "logs/plugin.log"
# ══════════════════════════════════════════════════════════════════════════════
# WARTUNGSMODUS
# ══════════════════════════════════════════════════════════════════════════════
# Sperrt den Server für normale Spieler während Wartungsarbeiten
maintenance:
# Aktiviert den Wartungsmodus (true = an, false = aus)
# Wenn aktiviert, können nur Spieler mit der Permission "nexuslobby.maintenance.bypass" joinen
enabled: false
# Nachricht die Spielern angezeigt wird, wenn sie gekickt werden
# Unterstützt Farbcodes und \n für Zeilenumbrüche
kick_message: "&cServer im Wartungsmodus! Du darfst nicht joinen." kick_message: "&cServer im Wartungsmodus! Du darfst nicht joinen."
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# VOID PROTECTION # VOID PROTECTION (SCHUTZ VOR LEERE)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Verhindert, dass Spieler in die Leere fallen # Verhindert dass Spieler in die Leere fallen und im Void sterben
void_protection: void_protection:
# Aktiviert den Void-Schutz (true = an, false = aus)
enabled: true enabled: true
# Teleportiert den Spieler zum Welt-Spawn
# Teleportiert Spieler zum Spawn zurück wenn sie in die Leere fallen
# true = Teleport zum Spawn, false = Spieler nimmt Schaden/stirbt
teleport_to_spawn: true teleport_to_spawn: true
# Nachricht beim Teleport (Leer lassen für keine Nachricht)
# Nachricht die dem Spieler beim Teleport angezeigt wird
# Leer lassen ("") für keine Nachricht
message: "&cDu bist in die Leere gefallen und wurdest teleportiert!" message: "&cDu bist in die Leere gefallen und wurdest teleportiert!"
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# DOUBLE JUMP # DOUBLE JUMP (DOPPELSPRUNG)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Erlaubt einen Doppelsprung in der Lobby # Erlaubt Spielern einen Doppelsprung in der Luft
doublejump: doublejump:
# Aktiviert die Doppelsprung-Funktion (true = an, false = aus)
enabled: true enabled: true
# Stärke des Sprungs nach oben
# Stärke des Sprungs nach oben (vertikale Geschwindigkeit)
# Standardwert: 1.0
# Höhere Werte = höherer Sprung, niedrigere Werte = flacherer Sprung
# Empfohlener Bereich: 0.5 - 2.0
velocity: 1.0 velocity: 1.0
# Vorwärts-Schub beim Sprung
# Vorwärts-Schub beim Sprung (horizontale Geschwindigkeit)
# Standardwert: 0.2
# Höhere Werte = mehr Vorwärtsschub, 0.0 = kein Vorwärtsschub
# Empfohlener Bereich: 0.0 - 0.5
horizontal: 0.2 horizontal: 0.2
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# PLAYER HIDER # PLAYER HIDER (SPIELER VERSTECKEN)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Item, um andere Spieler zu verstecken/anzuzeigen # Item zum Verstecken/Anzeigen anderer Spieler in der Lobby
hider: hider:
# Aktiviert die Player-Hider-Funktion (true = an, false = aus)
enabled: true enabled: true
# Material-Name (Muss ein gültiger Bukkit-Material-Name sein)
# Material des Items im Inventar
# Kann je nach Status wechseln (z.B. grüner/roter Farbstoff)
# Liste: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html
item: "REDSTONE" item: "REDSTONE"
# Slot im Inventar (0-8) # Slot im Inventar (0-8)
slot: 8 slot: 8
# Nachrichten für verschiedene Zustände
messages: messages:
# Anzeigename des Items und Nachricht, wenn alle sichtbar sind # Anzeigename und Nachricht wenn alle Spieler sichtbar sind
# Wird angezeigt wenn der Spieler auf das Item klickt und Spieler sichtbar sind
all: "&aAlle Spieler: &7Sichtbar" all: "&aAlle Spieler: &7Sichtbar"
# Anzeigename des Items und Nachricht, wenn alle versteckt sind
# Anzeigename und Nachricht wenn alle Spieler versteckt sind
# Wird angezeigt wenn der Spieler auf das Item klickt und Spieler versteckt sind
none: "&cKeine Spieler: &7Versteckt" none: "&cKeine Spieler: &7Versteckt"
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# BALL / SOCCER EINSTELLUNGEN # BALL / SOCCER EINSTELLUNGEN
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Aktiviert ein Fußball-System in der Lobby zum Spielen
ball: ball:
# Aktiviert das Ball-System (true = an, false = aus)
enabled: true enabled: true
# Der Spawnpunkt wird automatisch über /nexus ball setspawn hier gespeichert
# Spawn-Position des Balls
# Tipp: Nutze /nexuslobby ball setspawn um diese Position automatisch zu setzen
spawn: spawn:
# Name der Welt in der der Ball spawnt
world: "world" world: "world"
# X-Koordinate des Ball-Spawns
x: 10.5 x: 10.5
# Y-Koordinate des Ball-Spawns
y: 65.0 y: 65.0
# Z-Koordinate des Ball-Spawns
z: 10.5 z: 10.5
# Blickrichtung des Balls (normalerweise nicht relevant)
yaw: 0.0 yaw: 0.0
pitch: 0.0 pitch: 0.0
# Zeit in Sekunden, bis der Ball bei Inaktivität respawnt
# Zeit in Sekunden bis der Ball bei Inaktivität automatisch respawnt
# Nützlich wenn der Ball verloren geht oder stecken bleibt
# 0 = Kein automatischer Respawn
respawn_delay: 60 respawn_delay: 60
# ══════════════════════════════════════════════════════════════════════════
# TOR-DEFINITIONEN
# ══════════════════════════════════════════════════════════════════════════
# Tore werden automatisch per Befehl gesetzt und hier gespeichert.
# Manuell bearbeiten ist möglich, aber der Befehlsweg ist empfohlen.
#
# Anleitung zum Einrichten eines Tores:
# 1. Gehe zur INNEREN Ecke des Torrahmens (z.B. linke untere Ecke)
# /nexuslobby ball goal pos1
# 2. Gehe zur GEGENÜBERLIEGENDEN Ecke (rechte obere Ecke, auch 1-2 Blöcke
# hinter dem Tor in Schussrichtung für bessere Erkennung!)
# /nexuslobby ball goal pos2
# 3. Speichern (Team 1 = Blau, Team 2 = Rot):
# /nexuslobby ball goal save torBlau 1
#
# WICHTIG für zuverlässige Erkennung:
# - Die Box muss MINDESTENS 1.5 Blöcke Tiefe in Schussrichtung haben
# - Die Box muss MINDESTENS 3 Blöcke hoch sein (Y-Achse)
# - Nutze /nexuslobby ball goal show um die Box sichtbar zu machen
# - Nutze /nexuslobby ball goal debug um die Ball-Position live zu prüfen
#
# Beispiel-Eintrag (wird vom Plugin automatisch befüllt):
# goals:
# torBlau:
# pos1:
# world: world
# x: 100.0
# y: 64.0
# z: 198.0
# pos2:
# world: world
# x: 106.0
# y: 68.0
# z: 200.5 # <-- 2.5 Blöcke Tiefe in Z-Richtung!
# team: 1
# torRot:
# pos1:
# world: world
# x: 100.0
# y: 64.0
# z: -198.0
# pos2:
# world: world
# x: 106.0
# y: 68.0
# z: -200.5
# team: 2
#
# Tore werden hier automatisch eingetragen sobald du den save-Befehl nutzt:
goals: {}
# ══════════════════════════════════════════════════════════════════════════════
# Links
# ══════════════════════════════════════════════════════════════════════════════
#
# Benötigst du Hilfe oder Support?
# - Webseite: https://m-viper.de
# - Dokumentation: https://git.viper.ipv64.net/M_Viper/NexusLobby/wiki
# - Discord: https://discord.com/invite/FdRs4BRd8D
# - GitHub: https://git.viper.ipv64.net/M_Viper/NexusLobby

View File

@@ -1,5 +1,12 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# ============================================================= # =============================================================
# NexusLobby - Lebendige Dialoge v2 # Dialoge und Gespräche in der Lobby
# ============================================================= # =============================================================
conversations: conversations:

203
src/main/resources/lang.yml Normal file
View File

@@ -0,0 +1,203 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# =====================================================
# DEFAULT Language
# =====================================================
soccer_module_not_loaded:
de: "§cDas Fußball-Modul ist nicht geladen."
en: "§cThe soccer module is not loaded."
parkour_usage:
de: "§8[§6Nexus§8] §7Nutze: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>"
en: "§8[§6Nexus§8] §7Usage: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>"
parkour_run_aborted:
de: "§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen."
en: "§8[§6Nexus§8] §7Your current run was aborted."
parkour_besttimes_cleared:
de: "§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!"
en: "§8[§6Nexus§8] §aAll parkour best times have been cleared!"
parkour_track_removed:
de: "§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!"
en: "§8[§6Nexus§8] §cThe entire track (checkpoints & finish) has been removed!"
unknown_subcommand:
de: "§cUnbekannter Unterbefehl."
en: "§cUnknown subcommand."
parkour_npc_marked:
de: "§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!"
en: "§8[§6Nexus§8] §aArmorStand marked as parkour NPC!"
parkour_start_set:
de: "§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!"
en: "§8[§6Nexus§8] §aParkour start set at your position!"
scoreboard_usage:
de: "§cBenutzung: /nexus sb <on|off|admin|spieler>"
en: "§cUsage: /nexus sb <on|off|admin|player>"
scoreboard_module_disabled:
de: "§cScoreboard-Modul ist deaktiviert."
en: "§cScoreboard module is disabled."
info_header:
de: "§8§m--------------------------------------"
en: "§8§m--------------------------------------"
info_title:
de: "§6§lNexusLobby §7- Informationen"
en: "§6§lNexusLobby §7- Information"
info_spawn:
de: "§f/spawn §7- Zum Spawn"
en: "§f/spawn §7- To spawn"
info_parkour:
de: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"
en: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"
info_removeall:
de: "§f/nexus parkour removeall §7- Strecke löschen"
en: "§f/nexus parkour removeall §7- Remove track"
info_ball:
de: "§f/nexus ball setspawn §7- Fußball Spawn setzen"
en: "§f/nexus ball setspawn §7- Set soccer spawn"
info_setspawn:
de: "§f/nexus setspawn §7- Spawn setzen"
en: "§f/nexus setspawn §7- Set spawn"
info_scoreboard:
de: "§f/nexus sb <on|off> §7- Scoreboard"
en: "§f/nexus sb <on|off> §7- Scoreboard"
info_reload:
de: "§f/nexus reload §7- Config laden"
en: "§f/nexus reload §7- Reload config"
info_footer:
de: "§8§m--------------------------------------"
en: "§8§m--------------------------------------"
parkour_trainer_greeting:
de: "§6§lTrainer §8» §aViel Erfolg beim Parkour! Gib dein Bestes!"
en: "§6§lTrainer §8» §aGood luck with the parkour! Give it your best!"
update_available:
de: "§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!"
en: "§8[§6Nexus§8] §aA new §6update §afor §eNexusLobby §ais available!"
update_version:
de: "§8» §7Version: §c{old} §8-> §a{new}"
en: "§8» §7Version: §c{old} §8-> §a{new}"
update_download_link:
de: "§8» §6Klicke §e§l[HIER] §6zum Herunterladen."
en: "§8» §6Click §e§l[HERE] §6to download."
update_download_hover:
de: "§7Öffnet die Release-Seite"
en: "§7Opens the release page"
hider_all:
de: "§aAlle Spieler"
en: "§aAll players"
hider_none:
de: "§cKeine Spieler"
en: "§cNo players"
void_protection_message:
de: "§cDu wurdest vor dem Void gerettet!"
en: "§cYou were saved from the void!"
proxy_not_registered:
de: "§cProxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)."
en: "§cProxy connection not possible (BungeeCord channel not registered)."
connecting_to_server:
de: "§eVerbinde zu: §f{server}"
en: "§eConnecting to: §f{server}"
server_connect_error:
de: "§cFehler beim Verbinden zum Server."
en: "§cError connecting to server."
proxy_connect_error:
de: "§cProxy-Verbindung nicht möglich."
en: "§cProxy connection not possible."
armorstand_lookat_required:
de: "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!"
en: "§cYou must look directly at an ArmorStand (crosshair)!"
conv_link_usage:
de: "§cNutze: /nexuscmd conv link <Dialog-ID>"
en: "§cUsage: /nexuscmd conv link <Dialog-ID>"
conv_mark_npcs:
de: "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!"
en: "§cPlease mark at least the first two NPCs (select1 & select2)!"
conv_link_created:
de: "§a§lDauerhafte Verknüpfung erstellt!"
en: "§a§lPermanent link created!"
conv_link_npcs:
de: "§7Beteiligte NPCs: §e{count}"
en: "§7Linked NPCs: §e{count}"
conv_speaker_not_found:
de: "§cFehler: Sprecher 1 nicht gefunden."
en: "§cError: Speaker 1 not found."
conv_unlink_lookat:
de: "§cSchau den NPC an, dessen Verknüpfung du lösen willst!"
en: "§cLook at the NPC you want to unlink!"
conv_unlinked:
de: "§eNPC-Verknüpfung wurde aufgehoben."
en: "§eNPC link has been removed."
conv_start_usage:
de: "§cNutze: /nexuscmd conv start <ID>"
en: "§cUsage: /nexuscmd conv start <ID>"
conv_mark_two_npcs:
de: "§cBitte markiere mindestens zwei NPCs!"
en: "§cPlease mark at least two NPCs!"
npc_bubble_sent:
de: "§aNPC-Sprechblase gesendet."
en: "§aNPC speech bubble sent."
lookat_off:
de: "§cBlickkontakt aus."
en: "§cLook-at disabled."
lookat_on:
de: "§aBlickkontakt an."
en: "§aLook-at enabled."
name_removed:
de: "§eName entfernt."
en: "§eName removed."
name_set:
de: "§7Name gesetzt: {name}"
en: "§7Name set: {name}"
status_backup_saved:
de: "§8(Status-Backup wurde gespeichert)"
en: "§8(Status backup saved)"
command_bound:
de: "§aBefehl gebunden."
en: "§aCommand bound."
no_target:
de: "§cKein Ziel!"
en: "§cNo target!"
commands_and_tags:
de: "§6§lBefehle & Tags:"
en: "§6§lCommands & Tags:"
commands_removed:
de: "§eAlle Befehle gelöscht."
en: "§eAll commands removed."
welcome:
de: "Willkommen auf dem Server!"
en: "Welcome to the server!"
no_permission:
de: "§cKeine Berechtigung."
en: "§cNo permission."
only_player:
de: "§cDieser Befehl ist nur für Spieler!"
en: "§cThis command is for players only!"
parkour_checkpoint_set:
de: "§8[§6Nexus§8] §aParkour-Checkpoint gesetzt!"
en: "§8[§6Nexus§8] §aParkour checkpoint set!"
parkour_finish_set:
de: "§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"
en: "§8[§6Nexus§8] §aParkour finish set!"
teleport_spawn:
de: "§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert."
en: "§8[§6Nexus§8] §aYou have been teleported to spawn."
spawn_world_missing:
de: "§cFehler: Die Spawn-Welt existiert nicht."
en: "§cError: The spawn world does not exist."
spawn_not_set:
de: "§cEs wurde noch kein Spawn gesetzt."
en: "§cNo spawn has been set yet."
plugin_reloaded:
de: "§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!"
en: "§8[§6Nexus§8] §aPlugin successfully reloaded!"
spawn_set:
de: "§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"
en: "§8[§6Nexus§8] §aLobby spawn set successfully!"
silentjoin_on:
de: "§8[§6Nexus§8] §7Silent Join: §aAktiviert"
en: "§8[§6Nexus§8] §7Silent Join: §aEnabled"
silentjoin_off:
de: "§8[§6Nexus§8] §7Silent Join: §cDeaktiviert"
en: "§8[§6Nexus§8] §7Silent Join: §cDisabled"

View File

@@ -1,6 +1,6 @@
name: NexusLobby name: NexusLobby
main: de.nexuslobby.NexusLobby main: de.nexuslobby.NexusLobby
version: "1.1.0" version: "1.1.3"
api-version: "1.21" api-version: "1.21"
author: M_Viper author: M_Viper
description: Modular Lobby Plugin with an invisible Particle-Parkour system. description: Modular Lobby Plugin with an invisible Particle-Parkour system.
@@ -125,6 +125,18 @@ permissions:
nexuslobby.silentjoin: nexuslobby.silentjoin:
description: Versteckt die Join-Nachricht für den Spieler (Silent Join) description: Versteckt die Join-Nachricht für den Spieler (Silent Join)
default: op default: op
nexuslobby.join.maintenance:
description: Darf den Server trotz Wartungsmodus betreten
default: op
nexuslobby.security.bypass:
description: Umgeht den Security-Check (VPN/Country)
default: op
nexuslobby.scoreboard.admin:
description: Darf das Scoreboard im Admin-Modus nutzen
default: op
nexuslobby.border.bypass:
description: Umgeht die Lobby-Grenze
default: op
nexuslobby.parkour.admin: nexuslobby.parkour.admin:
description: Erlaubt das Setzen der unsichtbaren Parkour-Punkte (Start, Ziel, Checkpoints) description: Erlaubt das Setzen der unsichtbaren Parkour-Punkte (Start, Ziel, Checkpoints)
default: op default: op

View File

@@ -1,6 +1,12 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# ===================================================== # =====================================================
# NEXUSLOBBY DEFAULT LOBBY GAMERULES # DEFAULT LOBBY GAMERULES
# Minecraft 1.21.1
# ===================================================== # =====================================================
# ------------------------------------------------- # -------------------------------------------------

View File

@@ -1,5 +1,12 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# ----------------------------------------------------- # -----------------------------------------------------
# NEXUSLOBBY - VISUELLE EINSTELLUNGEN # VISUELLE EINSTELLUNGEN
# ----------------------------------------------------- # -----------------------------------------------------
# --- Tablist Einstellungen --- # --- Tablist Einstellungen ---