18 Commits
1.0.5 ... 1.1.1

Author SHA1 Message Date
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
7343f2cbf0 Upload pom.xml via GUI 2026-02-01 21:19:41 +00:00
6a892c45db Update from Git Manager GUI 2026-02-01 22:19:40 +01:00
ce43cac14f Update from Git Manager GUI 2026-01-31 21:24:57 +01:00
85aaadbf99 Upload pom.xml via GUI 2026-01-31 20:24:56 +00:00
369e226b3b Upload example-conversations.yml via GUI 2026-01-31 20:24:56 +00:00
ba20c2a498 example-conversations.yml hinzugefügt 2026-01-24 23:12:15 +00:00
250bf2fea6 README.md aktualisiert 2026-01-24 16:13:45 +00:00
64a03b539e Upload pom.xml via GUI 2026-01-24 16:03:51 +00:00
1db87e81f0 Update from Git Manager GUI 2026-01-24 17:03:48 +01:00
45 changed files with 4093 additions and 521 deletions

420
README.md
View File

@@ -1,200 +1,382 @@
## 🌐 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.
---
# NexusLobby
<p align="center">
<img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby">
</p>
Ein umfassendes Lobby-Plugin fur Minecraft Server (Paper/Spigot 1.21+) mit modularem Aufbau, 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)
![Java](https://img.shields.io/badge/Java-21+-orange)
![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**
Dieses Plugin ist urheberrechtlich geschutzt. Es gelten folgende Bedingungen:
Dieses Plugin ist urheberrechtlich geschützt. Es gelten folgende Bedingungen:
- Die Nutzung ist ausschliesslich fur den persönlichen Gebrauch gestattet
- Die Weitergabe, Verbreitung oder Veröffentlichung des Plugins ist **strengstens untersagt**
- Jegliche Anderung, Modifikation oder Dekompilierung des Codes ist **verboten**
- Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden
- Eine Weitergabe an Dritte ist ohne ausdrückliche schriftliche Genehmigung nicht gestattet
- Die Nutzung ist ausschließlich für den persönlichen Gebrauch gestattet.
- Die Weitergabe, Verbreitung oder Veröffentlichung des Plugins ist **strengstens untersagt**.
- Jegliche Änderung, Modifikation oder Dekompilierung des Codes ist **verboten**.
- Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden.
- Eine Weitergabe an Dritte ist ohne ausdrückliche schriftliche Genehmigung nicht gestattet.
Bei Verstoss gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
Bei Verstoß gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
---
## Features
## Features
### Lobby-Management
### 🤖 High-End NPC & ArmorStand System
- **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
- **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)
- **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
- **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn
- **Void-Schutz** - Automatischer Teleport bei Fall ins Void
- **Doppelsprung** - Konfigurierbarer Double-Jump mit Cooldown
- **Portal-System** - BungeeCord-Portale für nahtlose Server-Wechsel mit Partikel-Effekten
- **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus
- **Double-Jump** - Konfigurierbarer Doppelsprung mit Cooldown und Partikeln
- **Worldborder** - Unsichtbare Lobby-Begrenzung (Circle/Square)
- **Intro-Tour** - Cinematic Kamera-Tour für neue Spieler
### Visuelle Elemente
- **Scoreboard** - Anpassbares Sidebar-Scoreboard mit Animationen
- **Tablist** - Header und Footer mit PlaceholderAPI-Support
- **BossBar** - Animierte Boss-Bar mit wechselnden Nachrichten
- **ActionBar** - Permanente ActionBar-Nachrichten
### 🛡️ Sicherheit & Protection
- **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen via proxycheck.io API
- **Country-Blocker** - Geo-IP Filter (Whitelist/Blacklist für Länder)
- **Maintenance-Modus** - Vollständiger Wartungsmodus mit Whitelist-Funktion
- **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
### Sicherheit
- **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen (proxycheck.io API)
- **Country-Blocker** - Erlaubt nur bestimmte Lander (Whitelist/Blacklist)
- **Wartungsmodus** - Sperrt den Server für nicht-berechtigte Spieler
- **Lobby-Schutz** - Verhindert Griefing und unerwünschte Aktionen
### 📊 Visuelle Elemente
- **Scoreboard** - Vollständig animiert mit PlaceholderAPI-Support
- **Tablist** - Dynamische Header/Footer mit Permissions-Anzeige
- **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
### Zusatzliche Module
- **Portal-System** - Erstelle Portale fur Server-Wechsel (BungeeCord)
- **ArmorStand-Tools** - Bearbeite ArmorStands mit GUI und Command-Binding
- **Server-Switcher** - GUI-basierter Server-Wechsel
- **Spieler verstecken** - Toggle fur Spieler-Sichtbarkeit
- **Chat-Suppressor** - Unterdrückung fur globalen Chat
### 🎮 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
---
## Installation
## 📋 Befehle
### Voraussetzungen
- Paper/Spigot Server 1.21 oder höher
- Java 21 oder hoher
### Optionale Abhangigkeiten
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) - Für Platzhalter-Support
- [LuckPerms](https://luckperms.net/) - Für Berechtigungsverwaltung
### Schritte
1. Lade `NexusLobby.jar` herunter
2. Kopiere die JAR in den `plugins/` Ordner deines Servers
3. Starte den Server neu
4. Konfiguriere das Plugin in `plugins/NexusLobby/`
---
## Befehle
### Haupt-Commands
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/nexuslobby` | Hauptbefehl mit Hilfe | `nexuslobby.use` |
| `/nexuslobby reload` | Konfiguration neu laden | `nexuslobby.reload` |
| `/nexuslobby setspawn` | Spawn-Punkt setzen | `nexuslobby.setspawn` |
| `/build` | Build-Modus umschalten | `nexuslobby.build` |
| `/maintenance` | Wartungsmodus verwalten | `nexuslobby.maintenance` |
| `/portal` | Portal-System verwalten | `nexuslobby.portal` |
| `/armorstand` | ArmorStand-Editor | `nexuslobby.armorstand` |
| `/lobbysettings` | Spieler-Einstellungen | `nexuslobby.settings` |
| `/nexuslobby reload` | Lädt das Plugin neu | `nexuslobby.admin` |
| `/nexuslobby setspawn` | Setzt den Lobby-Spawn | `nexuslobby.admin` |
| `/nexuslobby silentjoin <on\|off>` | Versteckter Join/Quit | `nexuslobby.admin` |
| `/nexuslobby sb <on\|off\|admin\|spieler>` | Scoreboard-Kontrolle | `nexuslobby.admin` |
| `/spawn` | Teleport zum Spawn | `nexuslobby.spawn` |
### NPC & ArmorStand
| 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
## ⚙️ Konfiguration
### config.yml (Auszug)
### config.yml
```yaml
prefix: "&8[&6NexusLobby&8] "
# Spawn-Einstellungen
spawn:
teleport_on_join: true
teleport_on_respawn: true
world: "world"
x: 0.5
y: 100.0
y: 64.0
z: 0.5
yaw: 0.0
pitch: 0.0
double_jump:
# 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
cooldown: 3
velocity_y: 1.0
velocity_multiplier: 1.5
type: "SQUARE" # SQUARE oder CIRCLE
radius: 50.0
```
### settings.yml
```yaml
player_visibility: true
double_jump: true
flight: false
### conversations.yml
gamerules:
block_break: false
block_place: false
pvp: false
hunger: false
fall_damage: false
```yaml
conversations:
willkommen:
dialogue:
- "&eWächter: &7Willkommen auf dem Server!"
- "&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:
# UUID des ersten NPCs
"550e8400-e29b-41d4-a716-446655440000":
partner: "550e8400-e29b-41d4-a716-446655440001" # UUID des zweiten NPCs
dialog: willkommen
automated: true # Startet automatisch
```
### visuals.yml
### visuals.yml (Auszug)
```yaml
scoreboard:
# ActionBar-Nachrichten
actionbar:
enabled: true
title: "&6&lNEXUS LOBBY"
lines:
- "&7"
- "&fSpieler: &a%online%"
- "&fRang: &e%luckperms_primary_group_name%"
tablist:
enabled: true
header:
- "&6&lNEXUS NETWORK"
footer:
- "&7Spieler online: &a%online%"
speed: 3
messages:
- "&6&lWillkommen &8» &eauf unserem Server!"
- "&b&lViel Spaß &8» &7in der Lobby!"
# BossBar
bossbar:
enabled: true
interval: 40
messages:
- "&6Willkommen auf NexusNetwork!"
- text: "&6&lNexusLobby &8» &eVersion 1.1.0"
color: "YELLOW"
```
---
## Berechtigungen
## 🔐 Berechtigungen
### Admin-Berechtigungen
| Berechtigung | Beschreibung |
|--------------|--------------|
| `nexuslobby.*` | Alle Berechtigungen |
| `nexuslobby.use` | Grundlegende Nutzung |
| `nexuslobby.reload` | Konfiguration neu laden |
| `nexuslobby.setspawn` | Spawn setzen |
| `nexuslobby.build` | Build-Modus nutzen |
| `nexuslobby.maintenance` | Wartungsmodus verwalten |
| `nexuslobby.portal` | Portale verwalten |
| `nexuslobby.armorstand` | ArmorStand-Editor nutzen |
| `nexuslobby.admin` | Voller Zugriff auf alle System-Einstellungen |
| `nexuslobby.armorstand.cmd` | NPCs konfigurieren und Dialoge verknüpfen |
| `nexuslobby.armorstand.use` | Zugriff auf die ArmorStand-Editor GUI |
| `nexuslobby.build` | Berechtigung für den Baumodus |
| `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
| Berechtigung | Beschreibung |
|--------------|--------------|
| `nexuslobby.bypass.protection` | Lobby-Schutz umgehen |
| `nexuslobby.bypass.maintenance` | Wartungsmodus umgehen |
| `nexuslobby.bypass.vpn` | VPN-Blocker umgehen |
| `nexuslobby.bypass.country` | Country-Blocker umgehen |
| `nexuslobby.bypass.maintenance` | Server trotz Wartungsmodus betreten |
| `nexuslobby.bypass.vpn` | VPN-Check überspringen |
| `nexuslobby.bypass.country` | Country-Check überspringen |
---
## PlaceholderAPI
## 🔧 Technische Details
Das Plugin registriert eigene Platzhalter unter der Expansion `nexuslobby`:
### Systemanforderungen
- **Minecraft**: Paper/Spigot 1.21+ (Spigot-kompatibel, Paper empfohlen)
- **Java**: Java 21 oder höher
- **Dependencies**: LuckPerms, PlaceholderAPI (optional)
| Platzhalter | Beschreibung |
|-------------|--------------|
| `%nexuslobby_online%` | Spieler online |
| `%nexuslobby_max_players%` | Maximale Spieler |
| `%nexuslobby_maintenance%` | Wartungsmodus Status |
| `%nexuslobby_build_mode%` | Build-Modus Status |
| `%nexuslobby_double_jump%` | Double-Jump Status |
### 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)
---
## Support
## 📦 Installation
- [Wiki](../../../wiki) - Ausführliche Dokumentation
- [Issues](../../../issues) - Bug-Reports und Feature-Requests
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
---
**Copyright (c) 2025 - Alle Rechte vorbehalten**
## 🐛 Bekannte Issues & Lösungen
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.
### 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.

164
example-conversations.yml Normal file
View File

@@ -0,0 +1,164 @@
# =============================================================
# NexusLobby - Lebendige Dialoge v2
# =============================================================
conversations:
parkour_begruessung:
morgens:
dialogue:
- '&6&lTrainer &8» &fGuten Morgen! Zeit für Frühsport! *streckt sich*'
- '&6&lTrainer &8» &fWillkommen bei &e&lNexusLobby&f!'
- '&6&lTrainer &8» &eKlick mich an&f, um den Parkour zu starten!'
mittags:
dialogue:
- '&6&lTrainer &8» &fDie Sonne brennt, der Parkour wartet!'
- '&6&lTrainer &8» &fHast du das Zeug zum &aParkour-Meister&f?'
- '&6&lTrainer &8» &eKlick mich an&f, wenn du dich traust!'
abends:
dialogue:
- '&6&lTrainer &8» &fNoch eine Runde vor dem Schlafengehen?'
- '&6&lTrainer &8» &fZeig bei &e&lNexusLobby&f, was du noch drauf hast!'
- '&6&lTrainer &8» &eKlick mich an&f, für deinen neuen Rekord!'
owner_besuch:
morgens:
dialogue:
- '&dTimmy: &fMami, ist der Owner schon wach? &e*hüpft*'
- '&5Sarah: &7Bestimmt! Er schließt den Server auf.'
- '&dTimmy: &fIch zeig ihm meinen Teddy mit Krone! &a*stolz*'
- '&5Sarah: &7Leise sein, falls er noch arbeitet. *psst*'
mittags:
dialogue:
- '&dTimmy: &fDa ist das Büro! Ich seh die Fenster! &e*rennt*'
- '&5Sarah: &cStopp! &7Nicht so stürmisch, Timmy.'
- '&dTimmy: &fAber Teddy will den Schreibtisch sehen!'
- '&5Sarah: &7Komm an meine Hand. Wir klopfen ganz brav.'
abends:
dialogue:
- '&dTimmy: &fGuck! Im Büro brennt noch Licht! &b*zeigt*'
- '&5Sarah: &7Er arbeitet sicher noch an Updates.'
- '&dTimmy: &fIst er ein Roboter? Er schläft nie! &7*staunt*'
- '&5Sarah: &7Nein, er braucht nur viel Zaubertrank. *lacht*'
fussball_match:
morgens:
dialogue:
- '&eLeon: &fFinn! Aufwachen! Kick den Ball! &e*kickt*'
- '&6Finn: &8*gähnt* &7Mein Fuß schläft noch, Leon...'
- '&eLeon: &fKeine Ausreden! Pass an! &6*kickt hart*'
- '&6Finn: &fHuch! &7*stoppt unsicher* &fBin ja schon wach!'
mittags:
dialogue:
- '&eLeon: &fPass auf! Jetzt kommt mein Spezialschuss!'
- '&6Finn: &fDen hab ich! &e*macht Hechtsprung* &fJaaaa!'
- '&eLeon: &7Wie hast du den denn erwischt? &c*schmollt*'
- '&6Finn: &fIch kenne deine Tricks, Leon! *grinst*'
abends:
dialogue:
- '&eLeon: &7Ich seh den Ball kaum noch. &8*kneift Augen*'
- '&6Finn: &fEgal! Nächstes Tor gewinnt die Krone!'
- '&eLeon: &fHe! Das war Foul! Du hast geschubst! &c*meckert*'
- '&6Finn: &7Körpereinsatz! Fang mich doch! &a*rennt weg*'
baum_drama:
morgens:
dialogue:
- '&aLotte: &fDie Aussicht ist göttlich! &d*atmet tief*'
- '&bSchmidt: &7Lotte, komm runter! Es ist 7 Uhr morgens!'
- '&aLotte: &fIch beobachte Vögel. Ich bin jetzt einer!'
- '&bSchmidt: &7Du brauchst Kaffee, keinen Baum. &8*seufzt*'
mittags:
dialogue:
- '&aLotte: &fIch kann fliegen! Seht mich an! &7*balanciert*'
- '&bSchmidt: &cLotte, komm sofort vom Baum runter!'
- '&aLotte: &fIch bin ein stolzer Adler! &e*breitet Arme aus*'
- '&bSchmidt: &7Du bist eine Frau auf einer Birke! Abstieg!'
abends:
dialogue:
- '&aLotte: &fVon hier sieht man das Feuerwerk besser!'
- '&bSchmidt: &7Es wird kalt. Und da oben sind Spinnen!'
- '&aLotte: &fSpinnen? &7*schaut hektisch* &fWo?!'
- '&bSchmidt: &7Komm zur Leiter. Ich rette dich. &e*grinst*'
familien_spaziergang:
morgens:
dialogue:
- '&9Tom: &7Schaut mal diesen Sonnenaufgang an. &6*genießt*'
- '&5Eva: &7Lass dir Zeit. Die Welt wacht gerade erst auf.'
- '&dJasmin: &fPapa, die Blumen gehen auf! &e*bleibt stehen*'
- '&9Tom: &7Wollen wir zum Bäcker? Frische Brötchen!'
mittags:
dialogue:
- '&9Tom: &7Klar, Jasmin. Wünsch dir was ganz Besonderes!'
- '&5Eva: &7Nicht reinfallen! Das Wasser ist tief. &6*lacht*'
- '&dJasmin: &fDarf ich eine Münze werfen? Bitte! &d*hüpft*'
- '&9Tom: &7Ich wünsche mir... &7*kneift Augen zu* &f...Eis!'
abends:
dialogue:
- '&9Tom: &7Der ganze Spawn leuchtet jetzt schön. &b*guckt*'
- '&5Eva: &7Es wird so herrlich ruhig hier, oder? &d*lächelt*'
- '&dJasmin: &fPapa, darf ich auf deine Schultern? &7*gähnt*'
- '&9Tom: &7Hopp! &e*hebt sie hoch* &7Na, wie ist die Sicht?'
frauen_plausch:
morgens:
dialogue:
- '&9Marie: &7Guten Morgen! Du siehst verschlafen aus.'
- '&fPia: &7Die Party gestern war zu lang. Kaffee... &8*gähnt*'
- '&9Marie: &7Der Bäcker hat frische Zimtschnecken! Komm!'
- '&fPia: &fSorg dafür, dass ich nicht umkippe. &7*lacht*'
mittags:
dialogue:
- '&9Marie: &7Hast du den neuen Shop gesehen? Tolle Erze!'
- '&fPia: &fEcht jetzt? &7Ich dachte, die Mine ist zu.'
- '&9Marie: &7Die haben einen neuen Tunnel! Riesige Smaragde!'
- '&fPia: &fWahnsinn. Ich muss mein Inventar leeren!'
abends:
dialogue:
- '&9Marie: &7Hast du die Lichter am Hafen gesehen? &b*staunt*'
- '&fPia: &7Leider verpasst... ich musste Truhen sortieren.'
- '&9Marie: &7Du arbeitest zu viel, Pia. Genieß den Abend!'
- '&fPia: &7Stimmt. Morgen machen wir blau, okay?'
wettrennen_jungs:
morgens:
dialogue:
- '&bNico: &fErster am Portal! Lauf schneller, Lukas!'
- '&fLukas: &c*keucht* &fWarte! Meine Schuhe sind offen!'
- '&bNico: &fKeine Ausreden! Die Sonne lacht! &a*rennt*'
- '&fLukas: &fNa warte, ich krieg dich noch! &7*sprintet*'
mittags:
dialogue:
- '&bNico: &fKomm schon, du lahme Ente! &a*rast davon*'
- '&fLukas: &c*außer Atem* &7Ich... hab heute schon gefarmt!'
- '&bNico: &fErster! Gewonnen! &7*tanzt* &fHer mit dem Apfel!'
- '&fLukas: &fMorgen gewinnen meine Speed-Stiefel! &c*schwitzt*'
abends:
dialogue:
- '&bNico: &fLetztes Rennen! Verlierer poliert Rüstungen!'
- '&fLukas: &fDiesmal nicht, Nico! &e*nimmt Anlauf*'
- '&bNico: &fOh, jetzt wird es ernst? &7Fertig... LOS!'
- '&fLukas: &fJaaa! Ich bin vorne! Wer ist jetzt lahm?! &a*lacht*'
mutter_kind_spiel:
morgens:
dialogue:
- '&dMia: &fMami, schau! Meine Puppen warten schon. &d*zeigt*'
- '&5Sandra: &7Warten sie auf den Empfang, Mia?'
- '&dMia: &fJa, sie wollen zum Owner! &e*rückt Puppe recht*'
- '&5Sandra: &7Dann müssen sie brav Schlange stehen. &7*lächelt*'
mittags:
dialogue:
- '&dMia: &fGuck! Ein riesiges Hotel aus Klötzen! &e*stapelt*'
- '&5Sandra: &7Oha, wird das ein Dino-Hotel?'
- '&dMia: &fJa! Der Dino frisst nur Kekse! &e*füttert ihn*'
- '&5Sandra: &7Bau fleißig weiter, kleine Architektin.'
abends:
dialogue:
- '&dMia: &fMami, Dino ist müde. &7*gähnt laut*'
- '&5Sandra: &7Pack ihn gut in seine Decke ein, Mia.'
- '&dMia: &fMuss ich meine Klötze einpacken? &c*traurig*'
- '&5Sandra: &7Ja, aber morgen bauen wir ein Schloss!'
links:

14
pom.xml
View File

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

View File

@@ -13,12 +13,17 @@ import de.nexuslobby.modules.settings.LobbySettingsModule;
import de.nexuslobby.modules.portal.PortalManager;
import de.nexuslobby.modules.portal.PortalCommand;
import de.nexuslobby.modules.servers.ServerSwitcherListener;
import de.nexuslobby.modules.servers.ServerChecker;
import de.nexuslobby.modules.armorstandtools.*;
import de.nexuslobby.modules.gadgets.GadgetModule;
import de.nexuslobby.modules.hologram.HologramModule;
import de.nexuslobby.modules.mapart.MapArtModule;
import de.nexuslobby.modules.intro.IntroModule;
import de.nexuslobby.modules.border.BorderModule;
import de.nexuslobby.modules.parkour.ParkourManager;
import de.nexuslobby.modules.parkour.ParkourListener;
import de.nexuslobby.modules.player.PlayerInspectModule;
import de.nexuslobby.modules.ball.SoccerModule; // NEU
import de.nexuslobby.utils.*;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.md_5.bungee.api.chat.ClickEvent;
@@ -29,18 +34,24 @@ import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class NexusLobby extends JavaPlugin implements Listener {
@@ -56,32 +67,68 @@ public class NexusLobby extends JavaPlugin implements Listener {
private MapArtModule mapArtModule;
private IntroModule introModule;
private BorderModule borderModule;
private ParkourManager parkourManager;
private SoccerModule soccerModule; // NEU
private ConversationManager conversationManager;
private File visualsFile;
private FileConfiguration visualsConfig;
private boolean updateAvailable = false;
private String latestVersion = "";
private final Set<UUID> silentPlayers = new HashSet<>();
public static NexusLobby getInstance() {
return instance;
}
public Set<UUID> getSilentPlayers() {
return silentPlayers;
}
public ConversationManager getConversationManager() {
return conversationManager;
}
public ParkourManager getParkourManager() {
return parkourManager;
}
public SoccerModule getSoccerModule() { // NEU
return soccerModule;
}
@Override
public void onEnable() {
instance = this;
initCustomConfigs();
initCustomConfigs();
validateConfig();
// Lade die Sprachdatei
LangManager.load(this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
moduleManager = new ModuleManager(this);
ArmorStandGUI.init();
// --- Parkour & Conversation Initialisierung ---
this.parkourManager = new ParkourManager(this);
this.conversationManager = new ConversationManager(this);
ArmorStandGUI.init();
registerModules();
moduleManager.enableAll();
registerListeners();
ServerChecker.startGlobalChecker();
new ArmorStandLookAtModule();
startAutoConversationTimer();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new NexusLobbyExpansion().register();
getLogger().info("NexusLobby PAPI Expansion registriert.");
@@ -93,24 +140,22 @@ public class NexusLobby extends JavaPlugin implements Listener {
getLogger().info("NexusLobby v" + getDescription().getVersion() + " wurde erfolgreich gestartet.");
}
private void checkUpdates() {
new UpdateChecker(this).getVersion(version -> {
if (!this.getDescription().getVersion().equalsIgnoreCase(version)) {
this.updateAvailable = true;
this.latestVersion = version;
getLogger().warning("====================================================");
getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version);
getLogger().warning("Autor: M_Viper");
getLogger().warning("====================================================");
} else {
getLogger().info("NexusLobby ist aktuell (v" + version + ").");
private void startAutoConversationTimer() {
new BukkitRunnable() {
@Override
public void run() {
if (conversationManager != null) {
conversationManager.startAllAutomatedConversations();
}
}
});
}.runTaskTimer(this, 100L, 300L);
}
public void reloadPlugin() {
getLogger().info("Plugin Reload wird gestartet...");
Bukkit.getScheduler().cancelTasks(this);
if (moduleManager != null) {
moduleManager.disableAll();
}
@@ -119,8 +164,11 @@ public class NexusLobby extends JavaPlugin implements Listener {
visualsConfig = null;
reloadVisualsConfig();
Config.load();
if (conversationManager != null) {
conversationManager.setupFile();
}
// Border-Settings nach Config-Reload synchronisieren
if (borderModule != null) {
borderModule.reloadConfig();
}
@@ -128,15 +176,34 @@ public class NexusLobby extends JavaPlugin implements Listener {
if (portalManager != null) {
portalManager.loadPortals();
}
ArmorStandGUI.init();
if (moduleManager != null) {
moduleManager.enableAll();
}
ServerChecker.startGlobalChecker();
new ArmorStandLookAtModule();
startAutoConversationTimer();
getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen.");
}
private void checkUpdates() {
new UpdateChecker(this).getVersion(version -> {
if (!this.getDescription().getVersion().equalsIgnoreCase(version)) {
this.updateAvailable = true;
this.latestVersion = version;
getLogger().warning("====================================================");
getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version);
getLogger().warning("====================================================");
} else {
getLogger().info("NexusLobby ist aktuell (v" + version + ").");
}
});
}
private void registerModules() {
moduleManager.registerModule(new ProtectionModule());
moduleManager.registerModule(new ScoreboardModule());
@@ -153,6 +220,8 @@ public class NexusLobby extends JavaPlugin implements Listener {
this.dynamicArmorStandModule = new DynamicArmorStandModule();
moduleManager.registerModule(this.dynamicArmorStandModule);
moduleManager.registerModule(new ArmorStandStatusModule());
this.mapArtModule = new MapArtModule();
moduleManager.registerModule(this.mapArtModule);
@@ -172,6 +241,13 @@ public class NexusLobby extends JavaPlugin implements Listener {
this.tablistModule = new TablistModule();
moduleManager.registerModule(tablistModule);
// Player Inspect Modul registrieren
moduleManager.registerModule(new PlayerInspectModule());
// Soccer Modul registrieren
this.soccerModule = new SoccerModule(); // NEU
moduleManager.registerModule(this.soccerModule); // NEU
this.portalManager = new PortalManager(this);
moduleManager.registerModule(portalManager);
}
@@ -184,14 +260,38 @@ public class NexusLobby extends JavaPlugin implements Listener {
getServer().getPluginManager().registerEvents(new PlayerHider(), this);
getServer().getPluginManager().registerEvents(new MaintenanceListener(), this);
getServer().getPluginManager().registerEvents(new ASTListener(), this);
getServer().getPluginManager().registerEvents(new ParkourListener(this.parkourManager), this);
getServer().getPluginManager().registerEvents(new NPCClickListener(), this);
}
private void validateConfig() {
ConfigValidator validator = new ConfigValidator(this, getConfig());
validator.validate();
}
public class NPCClickListener implements Listener {
@EventHandler
public void onNPCClick(PlayerInteractAtEntityEvent event) {
Entity entity = event.getRightClicked();
Player player = event.getPlayer();
if (entity instanceof ArmorStand as) {
if (as.getScoreboardTags().contains("parkour_trainer")) {
player.performCommand("warp parkour");
player.sendMessage("§6§lTrainer §8» §aViel Erfolg beim Parkour! Gib dein Bestes!");
}
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
event.setJoinMessage(null);
if (silentPlayers.contains(player.getUniqueId())) {
event.setJoinMessage(null);
}
// Teleport zum Lobby-Spawn beim Beitreten
teleportToSpawn(player);
player.getInventory().clear();
@@ -199,7 +299,7 @@ public class NexusLobby extends JavaPlugin implements Listener {
BuildCommand.removePlayerFromBuildMode(player);
String defaultGmName = getConfig().getString("default-gamemode", "ADVENTURE");
String defaultGmName = getConfig().getString("lobby.default-gamemode", "Adventure");
try {
player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase()));
} catch (IllegalArgumentException e) {
@@ -207,17 +307,15 @@ public class NexusLobby extends JavaPlugin implements Listener {
}
if (player.hasPermission("nexuslobby.admin") && updateAvailable) {
player.sendMessage(" ");
player.sendMessage("§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!");
player.sendMessage("§8» §7Version: §c" + getDescription().getVersion() + " §8-> §a" + latestVersion);
TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen.");
player.sendMessage("");
player.sendMessage(de.nexuslobby.utils.LangManager.get("update_available"));
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"));
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,
new ComponentBuilder("§7Öffnet die Gitea Release-Seite").create()));
new ComponentBuilder(de.nexuslobby.utils.LangManager.get("update_download_hover")).create()));
player.spigot().sendMessage(link);
player.sendMessage(" ");
player.sendMessage("");
}
}
@@ -241,37 +339,26 @@ public class NexusLobby extends JavaPlugin implements Listener {
}
private void initCustomConfigs() {
if (!getDataFolder().exists()) {
getDataFolder().mkdirs();
}
if (!getDataFolder().exists()) getDataFolder().mkdirs();
File configFile = new File(getDataFolder(), "config.yml");
if (!configFile.exists()) {
saveResource("config.yml", false);
}
if (!configFile.exists()) saveResource("config.yml", false);
reloadConfig();
File settingsFile = new File(getDataFolder(), "settings.yml");
if (!settingsFile.exists()) {
saveResource("settings.yml", false);
}
if (!settingsFile.exists()) saveResource("settings.yml", false);
visualsFile = new File(getDataFolder(), "visuals.yml");
if (!visualsFile.exists()) {
saveResource("visuals.yml", false);
}
if (!visualsFile.exists()) saveResource("visuals.yml", false);
reloadVisualsConfig();
Config.load();
}
public void reloadVisualsConfig() {
if (visualsFile == null) {
visualsFile = new File(getDataFolder(), "visuals.yml");
}
if (visualsFile == null) visualsFile = new File(getDataFolder(), "visuals.yml");
visualsConfig = YamlConfiguration.loadConfiguration(visualsFile);
getLogger().info("visuals.yml erfolgreich geladen.");
}
public FileConfiguration getVisualsConfig() {
@@ -281,8 +368,23 @@ public class NexusLobby extends JavaPlugin implements Listener {
@Override
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");
if (moduleManager != null) moduleManager.disableAll();
// Disable alle Module (inkl. eigenes Cleanup)
if (moduleManager != null) {
moduleManager.disableAll();
}
getLogger().info("NexusLobby deaktiviert.");
}
@@ -290,22 +392,19 @@ public class NexusLobby extends JavaPlugin implements Listener {
LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule);
NexusLobbyCommand nexusCommand = new NexusLobbyCommand();
PluginCommand portalCmd = this.getCommand("portal");
if (portalCmd != null) {
portalCmd.setExecutor(new PortalCommand(portalManager));
portalCmd.setTabCompleter(tabCompleter);
if (getCommand("portal") != null) {
getCommand("portal").setExecutor(new PortalCommand(portalManager));
getCommand("portal").setTabCompleter(tabCompleter);
}
PluginCommand holoCmd = this.getCommand("holo");
if (holoCmd != null) {
holoCmd.setExecutor(new HoloCommand(hologramModule));
holoCmd.setTabCompleter(tabCompleter);
if (getCommand("holo") != null) {
getCommand("holo").setExecutor(new HoloCommand(hologramModule));
getCommand("holo").setTabCompleter(tabCompleter);
}
PluginCommand maintenanceCmd = this.getCommand("maintenance");
if (maintenanceCmd != null) {
maintenanceCmd.setExecutor(new MaintenanceCommand());
maintenanceCmd.setTabCompleter(tabCompleter);
if (getCommand("maintenance") != null) {
getCommand("maintenance").setExecutor(new MaintenanceCommand());
getCommand("maintenance").setTabCompleter(tabCompleter);
}
if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this));
@@ -322,32 +421,48 @@ public class NexusLobby extends JavaPlugin implements Listener {
getCommand("nexuscmd").setTabCompleter(tabCompleter);
}
// Haupt- und Spawn-Befehl Registrierung (NexusLobbyCommand verarbeitet beides)
PluginCommand nexusCmd = this.getCommand("nexuslobby");
if (nexusCmd != null) {
nexusCmd.setExecutor(nexusCommand);
nexusCmd.setTabCompleter(tabCompleter);
if (getCommand("nexuslobby") != null) {
getCommand("nexuslobby").setExecutor(nexusCommand);
getCommand("nexuslobby").setTabCompleter(tabCompleter);
}
PluginCommand spawnCmd = this.getCommand("spawn");
if (spawnCmd != null) {
spawnCmd.setExecutor(nexusCommand);
spawnCmd.setTabCompleter(tabCompleter);
if (getCommand("spawn") != null) {
getCommand("spawn").setExecutor(nexusCommand);
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("intro") != null) getCommand("intro").setTabCompleter(tabCompleter);
PluginCommand borderCmd = this.getCommand("border");
if (borderCmd != null) {
borderCmd.setExecutor(new BorderCommand());
borderCmd.setTabCompleter(tabCompleter);
if (getCommand("border") != null) {
getCommand("border").setExecutor(new BorderCommand());
getCommand("border").setTabCompleter(tabCompleter);
}
if (getCommand("serverswitcher") != null) {
ServerSwitcherListener serverSwitcher = new ServerSwitcherListener();
getCommand("serverswitcher").setExecutor(serverSwitcher);
}
}
public class NexusLobbyExpansion extends PlaceholderExpansion {
@Override public @NotNull String getIdentifier() { return "nexuslobby"; }
@Override public @NotNull String getAuthor() { return String.join(", ", NexusLobby.this.getDescription().getAuthors()); }
@Override public @NotNull String getAuthor() { return "M_Viper"; }
@Override public @NotNull String getVersion() { return NexusLobby.this.getDescription().getVersion(); }
@Override public boolean persist() { return true; }
@@ -357,15 +472,23 @@ public class NexusLobby extends JavaPlugin implements Listener {
if (params.equalsIgnoreCase("maintenance_status")) return MaintenanceListener.isMaintenance() ? "§cAktiv" : "§aDeaktiviert";
if (params.equalsIgnoreCase("version")) return NexusLobby.this.getDescription().getVersion();
if (params.equalsIgnoreCase("build_mode")) return BuildCommand.isInBuildMode(player) ? "§aAktiv" : "§cInaktiv";
if (params.equalsIgnoreCase("silent_join")) return silentPlayers.contains(player.getUniqueId()) ? "§aEin" : "§cAus";
if (params.equalsIgnoreCase("parkour_top")) {
return parkourManager != null ? parkourManager.getTopTen() : "N/A";
}
return null;
}
}
public ModuleManager getModuleManager() { return moduleManager; }
public PortalManager getPortalManager() { return portalManager; }
public TablistModule getTablistModule() { return tablistModule; }
public LobbySettingsModule getLobbySettingsModule() { return lobbySettingsModule; }
public ItemsModule getItemsModule() { return itemsModule; }
public GadgetModule getGadgetModule() { return gadgetModule; }
public HologramModule getHologramModule() { return hologramModule; }
public DynamicArmorStandModule getDynamicArmorStandModule() { return dynamicArmorStandModule; }
public MapArtModule getMapArtModule() { return mapArtModule; }
public IntroModule getIntroModule() { return introModule; }
public BorderModule getBorderModule() { return borderModule; }
}

View File

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

View File

@@ -33,6 +33,12 @@ public class GivePortalToolCommand implements CommandExecutor {
// Erstelle das Item
ItemStack wand = new ItemStack(Material.BLAZE_ROD);
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
meta.setDisplayName("§cPortal-Werkzeug");

View File

@@ -1,5 +1,6 @@
package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.portal.PortalManager;
import de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command;
@@ -25,61 +26,68 @@ public class LobbyTabCompleter implements TabCompleter {
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
List<String> suggestions = new ArrayList<>();
String cmdName = command.getName().toLowerCase();
// --- NexusLobby Hauptbefehl (/nexus) ---
if (command.getName().equalsIgnoreCase("nexuslobby") || command.getName().equalsIgnoreCase("nexus")) {
if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) {
if (args.length == 1) {
// Hier fügen wir 'setspawn' hinzu
if (sender.hasPermission("nexuslobby.admin")) {
suggestions.add("reload");
suggestions.add("setspawn");
suggestions.addAll(Arrays.asList("reload", "setspawn", "silentjoin", "parkour", "ball")); // NEU: ball
}
suggestions.add("sb");
} else if (args.length == 2 && args[0].equalsIgnoreCase("sb")) {
suggestions.add("on");
suggestions.add("off");
if (sender.hasPermission("nexuslobby.scoreboard.admin")) {
suggestions.add("admin");
suggestions.add("spieler");
} else if (args.length == 2) {
if (args[0].equalsIgnoreCase("sb")) {
suggestions.addAll(Arrays.asList("on", "off"));
if (sender.hasPermission("nexuslobby.scoreboard.admin")) {
suggestions.addAll(Arrays.asList("admin", "spieler"));
}
} else if (args[0].equalsIgnoreCase("silentjoin")) {
suggestions.addAll(Arrays.asList("on", "off"));
} else if (args[0].equalsIgnoreCase("parkour")) {
suggestions.addAll(Arrays.asList("setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall"));
} else if (args[0].equalsIgnoreCase("ball")) {
if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList("setspawn", "respawn", "remove"));
}
}
} else if (args.length == 3) {
if (args[0].equalsIgnoreCase("parkour") && args[1].equalsIgnoreCase("setcheckpoint")) {
suggestions.addAll(Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9"));
}
}
}
// --- Hologram Befehl ---
else if (command.getName().equalsIgnoreCase("holo")) {
else if (cmdName.equals("holo")) {
if (args.length == 1) {
suggestions.add("create");
suggestions.add("delete");
suggestions.addAll(Arrays.asList("create", "delete"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (hologramModule != null) {
suggestions.addAll(hologramModule.getHologramIds());
}
}
}
}
// --- Wartungsmodus ---
else if (command.getName().equalsIgnoreCase("maintenance")) {
else if (cmdName.equals("maintenance")) {
if (args.length == 1) {
suggestions.add("on");
suggestions.add("off");
suggestions.addAll(Arrays.asList("on", "off"));
}
}
// --- Portalsystem ---
else if (command.getName().equalsIgnoreCase("portal")) {
else if (cmdName.equals("portal")) {
if (args.length == 1) {
suggestions.add("create");
suggestions.add("delete");
suggestions.add("list");
suggestions.addAll(Arrays.asList("create", "delete", "list"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (portalManager != null) {
suggestions.addAll(portalManager.getPortalNames());
}
}
}
}
// --- MapArt ---
else if (command.getName().equalsIgnoreCase("mapart")) {
else if (cmdName.equals("mapart")) {
if (args.length == 1) {
suggestions.add("https://");
} else if (args.length == 2) {
@@ -88,31 +96,77 @@ public class LobbyTabCompleter implements TabCompleter {
}
// --- Intro System ---
else if (command.getName().equalsIgnoreCase("intro")) {
else if (cmdName.equals("intro")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("add", "clear", "start"));
}
}
// --- WorldBorder ---
else if (command.getName().equalsIgnoreCase("border")) {
else if (cmdName.equals("border")) {
if (args.length == 1) {
suggestions.add("circle");
suggestions.add("square");
suggestions.add("disable");
suggestions.addAll(Arrays.asList("circle", "square", "disable"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("circle")) {
suggestions.addAll(Arrays.asList("10", "25", "50", "100", "250"));
}
}
// --- NexusCmd & Tools (Verkürzte Logik) ---
else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ncmd")) {
// --- NexusCmd / ArmorStandTools ---
else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd") || cmdName.equals("conv")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("name", "list", "add", "remove"));
suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say"));
}
else if (args.length == 2) {
if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("name")) {
suggestions.addAll(Arrays.asList("<Anzeigename>", "none"));
} else if (args[0].equalsIgnoreCase("conv")) {
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) {
if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("conv")) {
if (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link")) {
if (NexusLobby.getInstance().getConversationManager() != null) {
suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds());
}
}
}
}
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("bungee", "console", "player"));
}
else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) {
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) {
suggestions.addAll(NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false));
}
}
}
// Filtert die Vorschläge basierend auf dem, was der Spieler bereits getippt hat
// --- ArmorStandTools Alternate ---
else if (cmdName.equals("astools") || cmdName.equals("nt") || cmdName.equals("ntools")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("dynamic", "lookat", "addplayer", "addconsole", "remove", "reload", "say"));
}
else if (args.length == 2 && args[0].equalsIgnoreCase("say")) {
suggestions.add("<Nachricht>");
}
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"));
}
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"));
}
}
// Filtert die Liste basierend auf der bisherigen Eingabe
return suggestions.stream()
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.collect(Collectors.toList());

View File

@@ -2,6 +2,7 @@ package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.ScoreboardModule;
import de.nexuslobby.modules.parkour.ParkourManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
@@ -10,109 +11,212 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class NexusLobbyCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("§cDieser Befehl ist nur für Spieler!");
if (!(sender instanceof Player player)) {
sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player"));
return true;
}
Player player = (Player) sender;
// ==========================================
// LOGIK FÜR /spawn
// ==========================================
if (command.getName().equalsIgnoreCase("spawn")) {
String cmdName = command.getName().toLowerCase();
ParkourManager pm = NexusLobby.getInstance().getParkourManager();
// --- DIREKTE KURZ-BEFEHLE ---
if (cmdName.equalsIgnoreCase("setstart")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
handleSetStart(player, pm);
return true;
}
if (cmdName.equalsIgnoreCase("setcheckpoint")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setCheckpoint(player, player.getLocation());
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_checkpoint_set"));
return true;
}
if (cmdName.equalsIgnoreCase("setfinish")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setFinishLocation(player.getLocation());
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set"));
return true;
}
// --- SPAWN BEFEHL ---
if (cmdName.equalsIgnoreCase("spawn")) {
FileConfiguration config = NexusLobby.getInstance().getConfig();
if (config.contains("spawn.world")) {
Location loc = getSpawnFromConfig(config);
if (loc != null) {
player.teleport(loc);
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 {
player.sendMessage("§cFehler: Die Spawn-Welt existiert nicht.");
player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_world_missing"));
}
} else {
player.sendMessage("§cEs wurde noch kein Spawn gesetzt.");
player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_not_set"));
}
return true;
}
// ==========================================
// LOGIK FÜR /nexus (Hauptbefehl)
// ==========================================
// Sub-Befehl: /nexus setspawn
if (args.length == 1 && args[0].equalsIgnoreCase("setspawn")) {
if (!player.hasPermission("nexuslobby.admin")) {
player.sendMessage("§cDu hast keine Berechtigung, den Spawn zu setzen.");
return true;
}
Location loc = player.getLocation();
FileConfiguration config = NexusLobby.getInstance().getConfig();
config.set("spawn.world", loc.getWorld().getName());
config.set("spawn.x", loc.getX());
config.set("spawn.y", loc.getY());
config.set("spawn.z", loc.getZ());
config.set("spawn.yaw", (double) loc.getYaw());
config.set("spawn.pitch", (double) loc.getPitch());
NexusLobby.getInstance().saveConfig();
player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!");
player.sendMessage("§7X: §f" + String.format("%.2f", loc.getX()) + " §7Y: §f" + String.format("%.2f", loc.getY()) + " §7Z: §f" + String.format("%.2f", loc.getZ()));
// --- HAUPTBEFEHL /NEXUSLOBBY oder /NEXUS ---
if (args.length == 0) {
sendInfo(player);
return true;
}
// Sub-Befehl: /nexus sb <on|off|admin|spieler>
if (args.length >= 2 && args[0].equalsIgnoreCase("sb")) {
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) {
player.sendMessage("§cScoreboard-Modul ist deaktiviert.");
return true;
}
switch (args[0].toLowerCase()) {
case "reload":
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
NexusLobby.getInstance().reloadPlugin();
player.sendMessage(de.nexuslobby.utils.LangManager.get("plugin_reloaded"));
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f);
break;
String sub = args[1].toLowerCase();
switch (sub) {
case "on": sbModule.setVisibility(player, true); break;
case "off": sbModule.setVisibility(player, false); break;
case "admin":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true);
else player.sendMessage("§cKeine Rechte.");
break;
case "spieler":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false);
else player.sendMessage("§cKeine Rechte.");
break;
default: player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>"); break;
}
return true;
case "setspawn":
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
Location loc = player.getLocation();
FileConfiguration config = NexusLobby.getInstance().getConfig();
config.set("spawn.world", loc.getWorld().getName());
config.set("spawn.x", loc.getX());
config.set("spawn.y", loc.getY());
config.set("spawn.z", loc.getZ());
config.set("spawn.yaw", (double) loc.getYaw());
config.set("spawn.pitch", (double) loc.getPitch());
NexusLobby.getInstance().saveConfig();
player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_set"));
break;
case "silentjoin":
if (!player.hasPermission("nexuslobby.silentjoin")) return noPerm(player);
if (NexusLobby.getInstance().getSilentPlayers().contains(player.getUniqueId())) {
NexusLobby.getInstance().getSilentPlayers().remove(player.getUniqueId());
player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_off"));
} else {
NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId());
player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_on"));
}
break;
case "sb":
handleScoreboard(player, args);
break;
case "ball": // NEU: Weiterleitung an das SoccerModule
if (NexusLobby.getInstance().getSoccerModule() != null) {
return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args);
}
player.sendMessage(de.nexuslobby.utils.LangManager.get("soccer_module_not_loaded"));
break;
case "parkour":
if (args.length < 2) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage"));
return true;
}
String sub = args[1].toLowerCase();
if (!player.hasPermission("nexuslobby.admin") && !sub.equals("reset")) return noPerm(player);
switch (sub) {
case "setstart":
handleSetStart(player, pm);
break;
case "setfinish":
pm.setFinishLocation(player.getLocation());
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set"));
break;
case "setcheckpoint":
pm.setCheckpoint(player, player.getLocation());
break;
case "reset":
pm.stopParkour(player);
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_run_aborted"));
break;
case "clear":
pm.clearStats();
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_besttimes_cleared"));
break;
case "removeall":
pm.removeAllPoints();
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_track_removed"));
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f);
break;
default:
player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand"));
break;
}
break;
default:
sendInfo(player);
break;
}
// Sub-Befehl: /nexus reload
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
if (!player.hasPermission("nexuslobby.admin")) {
player.sendMessage("§cKeine Rechte.");
return true;
}
NexusLobby.getInstance().reloadPlugin();
player.sendMessage("§7[§6NexusLobby§7] §aPlugin erfolgreich neu geladen!");
return true;
}
// Info-Screen
sendInfo(player);
return true;
}
private void handleSetStart(Player player, ParkourManager pm) {
// NPC Erkennung (Blickrichtung auf ArmorStand)
ArmorStand targetAs = null;
List<Entity> nearby = player.getNearbyEntities(4, 4, 4);
for (Entity e : nearby) {
if (e instanceof ArmorStand as) {
double dot = player.getLocation().getDirection().dot(as.getLocation().toVector().subtract(player.getLocation().toVector()).normalize());
if (dot > 0.9) {
targetAs = as;
break;
}
}
}
if (targetAs != null) {
targetAs.addScoreboardTag("parkour_npc");
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_npc_marked"));
}
pm.setStartLocation(player.getLocation());
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_start_set"));
}
private boolean noPerm(Player player) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
return true;
}
private void handleScoreboard(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_usage"));
return;
}
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_module_disabled"));
return;
}
String sub = args[1].toLowerCase();
switch (sub) {
case "on": sbModule.setVisibility(player, true); break;
case "off": sbModule.setVisibility(player, false); break;
case "admin":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true);
else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
break;
case "spieler":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false);
else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
break;
}
}
private Location getSpawnFromConfig(FileConfiguration config) {
World world = Bukkit.getWorld(config.getString("spawn.world", "world"));
if (world == null) return null;
@@ -122,14 +226,16 @@ public class NexusLobbyCommand implements CommandExecutor {
}
private void sendInfo(Player player) {
String version = NexusLobby.getInstance().getDescription().getVersion();
player.sendMessage("§8§m--------------------------------------");
player.sendMessage("§6§lNexusLobby §7- Informationen");
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_header"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_title"));
player.sendMessage("");
player.sendMessage("§f/spawn §7- Zum Spawn teleportieren");
player.sendMessage("§f/nexus setspawn §7- Spawn setzen");
player.sendMessage("§f/nexus reload §7- Konfiguration laden");
player.sendMessage("§f/nexus sb <on|off> §7- Scoreboard");
player.sendMessage("§8§m--------------------------------------");
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_spawn"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_parkour"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_removeall"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_ball"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_setspawn"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_scoreboard"));
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_reload"));
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
String gadgetName = colorize(config.getString("items.lobby-tools.gadget.displayname", "&bGadgets"));
if (displayName.equals(gadgetName)) {
// Öffnet die GUI aus dem GadgetModule
NexusLobby.getInstance().getGadgetModule().openGUI(player);
// Öffnet die GUI aus dem GadgetModule (falls vorhanden)
var gadgetModule = NexusLobby.getInstance().getGadgetModule();
if (gadgetModule != null) {
gadgetModule.openGUI(player);
}
event.setCancelled(true);
return;
}

View File

@@ -18,6 +18,7 @@ import java.util.UUID;
public class ScoreboardModule implements Module {
private final NexusLobby plugin = NexusLobby.getInstance();
private boolean placeholderAPIEnabled;
// Speicher für die aktuellen Spieler-Einstellungen (bis zum Restart/Reload)
private final Set<UUID> hiddenPlayers = new HashSet<>();
@@ -33,6 +34,8 @@ public class ScoreboardModule implements Module {
FileConfiguration vConfig = plugin.getVisualsConfig();
if (!vConfig.getBoolean("scoreboard.enabled", true)) return;
placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null;
new BukkitRunnable() {
@Override
public void run() {
@@ -124,7 +127,14 @@ public class ScoreboardModule implements Module {
}
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);
}

View File

@@ -1,5 +1,9 @@
package de.nexuslobby.modules.armorstandtools;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
@@ -12,42 +16,104 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.EulerAngle;
import java.util.UUID;
public class ASTListener implements Listener {
/**
* Erkennt den Rechtsklick auf den ArmorStand.
* Öffnet bei Sneak + Rechtsklick die Haupt-GUI.
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInteract(PlayerInteractAtEntityEvent event) {
// Sicherstellen, dass nur die Haupthand zählt (verhindert Doppel-Trigger)
if (event.getHand() != EquipmentSlot.HAND) return;
if (!(event.getRightClicked() instanceof ArmorStand as)) return;
Player p = event.getPlayer();
// Prüfen ob Spieler schleicht (Shift)
// --- 1. FALL: SNEAKING -> Editor öffnen ---
if (p.isSneaking()) {
if (p.hasPermission("nexuslobby.armorstand.use")) {
event.setCancelled(true);
// ArmorStand für diesen Spieler zwischenspeichern
AST.selectedArmorStand.put(p.getUniqueId(), as);
// Haupt-GUI öffnen
new ArmorStandGUI(as, p);
}
return;
}
// --- 2. FALL: NORMALER KLICK -> Dialog manuell triggern ---
// Dies triggert das Gruppensystem im ConversationManager
checkAndTriggerDialog(as, p);
// --- 3. FALL: Befehle ausführen (Bungee, etc.) ---
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("ascmd:")) {
String[] parts = tag.split(":");
if (parts.length < 5) continue;
String type = parts[3].toLowerCase();
String command = parts[4];
event.setCancelled(true);
switch (type) {
case "bungee":
connectToServer(p, command);
break;
case "player":
p.performCommand(command);
break;
case "console":
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.replace("%player%", p.getName()));
break;
}
break;
}
}
}
/**
* Verwaltet alle Klicks innerhalb der verschiedenen GUIs.
* 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).
*/
private void checkAndTriggerDialog(ArmorStand as, Player p) {
String groupName = null;
String partnerUUIDString = null;
for (String tag : as.getScoreboardTags()) {
// conv_id enthält jetzt den Gruppennamen aus der conversations.yml
if (tag.startsWith("conv_id:")) groupName = tag.split(":")[1];
if (tag.startsWith("conv_partner:")) partnerUUIDString = tag.split(":")[1];
}
if (groupName != null && partnerUUIDString != null) {
try {
UUID partnerUUID = UUID.fromString(partnerUUIDString);
// Wir rufen playConversation auf. Der Manager entscheidet selbst
// anhand der Uhrzeit, ob er morgens, mittags oder nachts abspielt.
NexusLobby.getInstance().getConversationManager().playConversation(
as.getUniqueId(),
partnerUUID,
groupName
);
} catch (Exception ignored) {
// Falls die UUID oder Gruppe ungültig ist
}
}
}
/**
* Sendet den Spieler per Plugin-Message an BungeeCord
*/
private void connectToServer(Player player, String server) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(server);
player.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", out.toByteArray());
player.sendMessage("§8[§6Nexus§8] §7Du wirst mit §e" + server + " §7verbunden...");
}
@EventHandler(priority = EventPriority.LOW)
public void onInventoryClick(InventoryClickEvent event) {
String title = event.getView().getTitle();
// Prüfen, ob es eine unserer GUIs ist
if (!title.equals("Nexus ArmorStand Editor") &&
!title.equals("Pose: Körperteil wählen") &&
!title.startsWith("Achsen:")) return;
@@ -64,19 +130,16 @@ public class ASTListener implements Listener {
ItemStack item = event.getCurrentItem();
if (item == null || item.getType() == Material.AIR) return;
// --- 1. LOGIK: HAUPTMENÜ ---
if (title.equals("Nexus ArmorStand Editor")) {
ArmorStandTool tool = ArmorStandTool.get(item);
if (tool != null) {
tool.execute(as, p);
// GUI erneuern (außer sie wurde durch execute geschlossen)
// Menü aktualisieren, falls wir noch im selben Editor sind
if (p.getOpenInventory().getTitle().equals(title)) {
new ArmorStandGUI(as, p);
}
}
}
// --- 2. LOGIK: KÖRPERTEIL-AUSWAHL ---
else if (title.equals("Pose: Körperteil wählen")) {
if (item.getType() == Material.BARRIER) {
new ArmorStandGUI(as, p);
@@ -95,8 +158,6 @@ public class ASTListener implements Listener {
ArmorStandPoseGUI.openAxisDetailMenu(p, as, targetPart);
}
}
// --- 3. LOGIK: ACHSEN-FEINJUSTIERUNG ---
else if (title.startsWith("Achsen:")) {
if (item.getType() == Material.ARROW) {
ArmorStandPoseGUI.openPartSelectionMenu(p, as);
@@ -106,7 +167,6 @@ public class ASTListener implements Listener {
String partName = title.split(": ")[1];
ArmorStandTool tool = ArmorStandTool.valueOf(partName);
// Berechnung des Winkels (Links/Rechts & Shift)
double change = event.isShiftClick() ? Math.toRadians(1) : Math.toRadians(15);
if (event.isRightClick()) change *= -1;
@@ -122,7 +182,6 @@ public class ASTListener implements Listener {
}
ArmorStandPoseGUI.setAngleForPart(as, tool, newPose);
// GUI aktualisieren um Werte in der Lore anzuzeigen
ArmorStandPoseGUI.openAxisDetailMenu(p, as, partName);
}
}

View File

@@ -1,17 +1,30 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Particle;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.util.EulerAngle;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Set;
import java.util.List;
import java.util.UUID;
/**
* ArmorStandCmdExecutor - Erweiterte Steuerung für ArmorStand-Interaktionen.
* Nutzt Raytracing für präzise Auswahl und permanentes Dialog-Linking sowie Status-Backup.
* Erweitert um Unterstützung für Gruppen-Dialoge (bis zu 4 NPCs).
* * Update: Bedrock-Kompatibilität für Namen korrigiert.
*/
public class ArmorStandCmdExecutor implements CommandExecutor {
private final String prefix = "§8[§6Nexus§8] ";
@@ -21,78 +34,224 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.armorstand.cmd")) {
p.sendMessage(prefix + "§cKeine Berechtigung!");
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("no_permission"));
return true;
}
// 1. Priorität: Name setzen (verwendet den in der Map gespeicherten AS aus der GUI)
if (args.length >= 2 && args[0].equalsIgnoreCase("name")) {
ArmorStand target = AST.selectedArmorStand.get(p.getUniqueId());
if (target == null || !target.isValid()) {
p.sendMessage(prefix + "§cBitte wähle zuerst einen ArmorStand im GUI (Sneak-Rechtsklick) aus!");
return true;
if (args.length == 0) return sendHelp(p);
// --- CONVERSATION BEFEHLE ---
if (args[0].equalsIgnoreCase("conv")) {
if (args.length < 2) {
return sendConvHelp(p);
}
switch (args[1].toLowerCase()) {
case "select1":
case "select2":
case "select3":
case "select4":
ArmorStand target = getTargetArmorStand(p);
if (target == null) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required"));
return true;
}
// Dynamische Ermittlung des Slots (1, 2, 3 oder 4)
String slotStr = args[1].toLowerCase().replace("select", "");
String metaKey = "conv_npc" + slotStr;
UUID targetUUID = target.getUniqueId();
p.setMetadata(metaKey, new FixedMetadataValue(NexusLobby.getInstance(), targetUUID.toString()));
p.sendMessage(prefix + "§aNPC §e" + slotStr + " §amarkiert (§7" + targetUUID.toString().substring(0, 8) + "...§a)");
p.spawnParticle(Particle.WITCH, target.getLocation().add(0, 1.0, 0), 15, 0.2, 0.2, 0.2, 0.05);
break;
case "link":
if (args.length < 3) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_usage"));
return true;
}
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_npcs"));
return true;
}
UUID id1 = UUID.fromString(p.getMetadata("conv_npc1").get(0).asString());
UUID id2 = UUID.fromString(p.getMetadata("conv_npc2").get(0).asString());
// Optionale Partner 3 und 4 abrufen falls vorhanden
UUID id3 = p.hasMetadata("conv_npc3") ? UUID.fromString(p.getMetadata("conv_npc3").get(0).asString()) : null;
UUID id4 = p.hasMetadata("conv_npc4") ? UUID.fromString(p.getMetadata("conv_npc4").get(0).asString()) : null;
String dialogId = args[2];
Entity entity1 = Bukkit.getEntity(id1);
if (entity1 instanceof ArmorStand as1) {
// Vorhandene Tags säubern
as1.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_partner") || tag.startsWith("conv_id:"));
// Tags für Partner setzen
as1.addScoreboardTag("conv_partner:" + id2.toString());
if (id3 != null) as1.addScoreboardTag("conv_partner2:" + id3.toString());
if (id4 != null) as1.addScoreboardTag("conv_partner3:" + id4.toString());
as1.addScoreboardTag("conv_id:" + dialogId);
// Im Manager speichern (Nutzt die erweiterte Methode für Gruppen)
NexusLobby.getInstance().getConversationManager().saveLinkExtended(id1, id2, id3, id4, dialogId);
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_created"));
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);
// Metadaten nach dem Linken aufräumen
p.removeMetadata("conv_npc1", NexusLobby.getInstance());
p.removeMetadata("conv_npc2", NexusLobby.getInstance());
p.removeMetadata("conv_npc3", NexusLobby.getInstance());
p.removeMetadata("conv_npc4", NexusLobby.getInstance());
} else {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_speaker_not_found"));
}
break;
case "unlink":
ArmorStand targetUnlink = getTargetArmorStand(p);
if (targetUnlink == null) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_unlink_lookat"));
return true;
}
// Ingame Tags entfernen (alle conv_ Tags)
targetUnlink.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_"));
// Aus Konfiguration löschen
NexusLobby.getInstance().getConversationManager().removeLink(targetUnlink.getUniqueId());
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);
break;
case "start":
if (args.length < 3) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_start_usage"));
return true;
}
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_two_npcs"));
return true;
}
// Liste der Teilnehmer für den Startbefehl erstellen
List<UUID> participants = new ArrayList<>();
participants.add(UUID.fromString(p.getMetadata("conv_npc1").get(0).asString()));
participants.add(UUID.fromString(p.getMetadata("conv_npc2").get(0).asString()));
if (p.hasMetadata("conv_npc3")) participants.add(UUID.fromString(p.getMetadata("conv_npc3").get(0).asString()));
if (p.hasMetadata("conv_npc4")) participants.add(UUID.fromString(p.getMetadata("conv_npc4").get(0).asString()));
// Gespräch über die Gruppen-Methode starten
NexusLobby.getInstance().getConversationManager().playConversationGroup(participants, args[2]);
break;
default:
return sendConvHelp(p);
}
return true;
}
// --- STANDARD TOOLS (LOOKAT / NAME / ADD / SAY) ---
ArmorStand target = getTargetArmorStand(p);
if (args[0].equalsIgnoreCase("say") && args.length >= 2) {
if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
String text = buildString(args, 1);
String colored = ChatColor.translateAlternateColorCodes('&', text);
// Nutzt die showBubble-Logik aus dem ConversationManager (Ohne Partner-Zwang)
NexusLobby.getInstance().getConversationManager().showBubble(target, colored);
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("npc_bubble_sent"));
return true;
}
if (args[0].equalsIgnoreCase("lookat")) {
if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
if (target.getScoreboardTags().contains("as_lookat")) {
target.removeScoreboardTag("as_lookat");
target.setHeadPose(new EulerAngle(0, 0, 0));
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_off"));
} else {
target.addScoreboardTag("as_lookat");
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_on"));
}
return true;
}
if (args[0].equalsIgnoreCase("name") && args.length >= 2) {
if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
String nameInput = buildString(args, 1);
// Wichtig: Alle alten Namens-Tags entfernen für Konsistenz
target.getScoreboardTags().removeIf(tag -> tag.startsWith("asname:") || tag.startsWith("as_displayname:"));
if (nameInput.equalsIgnoreCase("none")) {
target.setCustomName("");
target.setCustomNameVisible(false);
p.sendMessage(prefix + "§eName entfernt.");
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_removed"));
} else {
String colored = ChatColor.translateAlternateColorCodes('&', nameInput);
target.setCustomName(colored);
target.setCustomNameVisible(true);
p.sendMessage(prefix + "§7Name gesetzt: " + colored);
// Wir speichern es unter as_displayname, damit das StatusModule es findet
// ":" wird durch "§§" ersetzt, um Probleme in Scoreboard-Tags zu vermeiden
target.addScoreboardTag("as_displayname:" + nameInput.replace(":", "§§"));
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_set").replace("{name}", colored));
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("status_backup_saved"));
}
return true;
}
// 2. Priorität: Action-Commands (verwendet Nearby-Suche für /nexuscmd add...)
ArmorStand target = getNearbyArmorStand(p);
if (target == null) {
p.sendMessage(prefix + "§cKein ArmorStand in der Nähe (4 Blöcke) gefunden!");
if (args[0].equalsIgnoreCase("add") && args.length >= 5) {
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 cmdStr = buildString(args, 4);
target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr);
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("command_bound"));
return true;
}
if (args.length >= 5 && args[0].equalsIgnoreCase("add")) {
String type = args[3].toLowerCase();
String cmd = buildString(args, 4);
if (!type.equals("player") && !type.equals("console") && !type.equals("bungee")) {
p.sendMessage(prefix + "§cTypen: §eplayer, console, bungee");
return true;
}
target.addScoreboardTag("ascmd:" + type + ":" + cmd);
p.sendMessage(prefix + "§aBefehl an ArmorStand gebunden!");
if (args[0].equalsIgnoreCase("list")) {
if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; }
p.sendMessage(de.nexuslobby.utils.LangManager.get("commands_and_tags"));
target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t));
return true;
}
if (args.length >= 1 && args[0].equalsIgnoreCase("list")) {
p.sendMessage("§6§lBefehle auf diesem ArmorStand:");
for (String tag : target.getScoreboardTags()) {
if (tag.startsWith("ascmd:")) {
p.sendMessage(" §8» §e" + tag.replace("ascmd:", ""));
}
}
return true;
}
if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) {
Set<String> tags = target.getScoreboardTags();
for (String tag : new ArrayList<>(tags)) {
if (tag.startsWith("ascmd:")) target.removeScoreboardTag(tag);
}
p.sendMessage(prefix + "§eAlle Befehle entfernt.");
if (args[0].equalsIgnoreCase("remove")) {
if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; }
target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("commands_removed"));
return true;
}
return sendHelp(p);
}
private ArmorStand getNearbyArmorStand(Player p) {
for (Entity e : p.getNearbyEntities(4, 4, 4)) {
if (e instanceof ArmorStand as) return as;
private ArmorStand getTargetArmorStand(Player p) {
RayTraceResult result = p.getWorld().rayTraceEntities(
p.getEyeLocation(),
p.getLocation().getDirection(),
8,
0.3,
entity -> entity instanceof ArmorStand
);
if (result != null && result.getHitEntity() instanceof ArmorStand as) {
return as;
}
return null;
}
@@ -105,12 +264,26 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
return sb.toString();
}
private boolean sendConvHelp(Player p) {
p.sendMessage(" ");
p.sendMessage("§6§lConversation Setup:");
p.sendMessage("§e/nexuscmd conv select1-4 §7- NPCs markieren");
p.sendMessage("§e/nexuscmd conv link <ID> §7- Speichern");
p.sendMessage("§e/nexuscmd conv unlink §7- Verknüpfung lösen");
p.sendMessage("§e/nexuscmd conv start <ID> §7- Testen");
p.sendMessage(" ");
return true;
}
private boolean sendHelp(Player p) {
p.sendMessage("§6§lNexus Command-Binder Hilfe:");
p.sendMessage("§e/nexuscmd name <Text> §7- Namen setzen (AS vorher anklicken)");
p.sendMessage("§e/nexuscmd add 0 0 <type> <cmd> §7- Befehl binden");
p.sendMessage("§e/nexuscmd list §7- Befehle anzeigen");
p.sendMessage("§e/nexuscmd remove §7- Befehle löschen");
p.sendMessage("§6§lNexus Tools Hilfe:");
p.sendMessage("§e/nexuscmd name <Text> §7- Setzt Namen (Bedrock-Safe)");
p.sendMessage("§e/nexuscmd say <Text> §7- Erstellt eine Sprechblase");
p.sendMessage("§e/nexuscmd lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/nexuscmd add <s1> <s2> bungee <Server> §7- Bungee-Bindung");
p.sendMessage("§e/nexuscmd conv §7- Gesprächs-Menü");
p.sendMessage("§e/nexuscmd list §7- Zeigt alle Tags");
p.sendMessage("§e/nexuscmd remove §7- Löscht Befehle");
return true;
}
}

View File

@@ -9,16 +9,39 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.EulerAngle;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
import java.util.UUID;
/**
* ArmorStandCommand - Vollständige Steuerung für ArmorStands.
* Inklusive Dynamic-Modus Erkennung und visueller Rückmeldung.
* Inklusive Dynamic-Modus, Look-At Funktion, Befehls-Slots und Conversation-Sprecher.
*/
public class ArmorStandCommand implements CommandExecutor {
// Statische Variablen für die aktuell markierten Sprecher
private static UUID speaker1;
private static UUID speaker2;
// Getter-Methoden für die NexusLobby Hauptklasse
public static UUID getSpeaker1() {
return speaker1;
}
public static UUID getSpeaker2() {
return speaker2;
}
// Setter-Methoden (werden vom ASTListener oder der GUI aufgerufen)
public static void setSpeaker1(UUID id) {
speaker1 = id;
}
public static void setSpeaker2(UUID id) {
speaker2 = id;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player p)) {
@@ -72,36 +95,56 @@ public class ArmorStandCommand implements CommandExecutor {
}
if (NexusLobby.getInstance().getDynamicArmorStandModule() != null) {
// Toggle Logik
if (target.getScoreboardTags().contains("as_dynamic")) {
target.removeScoreboardTag("as_dynamic");
p.sendMessage(prefix + "§c§l[-] §7Dynamic-Modus §cdeaktiviert§7.");
p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 15, 0.3, 0.3, 0.3, 0.05);
} else {
target.addScoreboardTag("as_dynamic");
p.sendMessage(prefix + "§a§l[+] §7Dynamic-Modus §aaktiviert§7 (Wetter/Zeit).");
// Visueller Erfolgseffekt (Grüne Sternchen)
p.sendMessage(prefix + "§a§l[+] §7Dynamic-Modus §aaktiviert§7.");
p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 25, 0.5, 0.5, 0.5, 0.1);
}
// Internes Update sofort triggern
NexusLobby.getInstance().getDynamicArmorStandModule().toggleDynamicStatus(target);
} else {
p.sendMessage(prefix + "§cFehler: Dynamic-Modul ist nicht aktiv!");
}
break;
case "lookat":
if (!p.hasPermission("nexuslobby.armorstand.lookat")) {
p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Look-At!");
return true;
}
if (target.getScoreboardTags().contains("as_lookat")) {
target.removeScoreboardTag("as_lookat");
// Kopf-Pose zurücksetzen für sauberen Übergang
target.setHeadPose(new EulerAngle(0, 0, 0));
p.sendMessage(prefix + "§c§l[-] §7Blickkontakt §cdeaktiviert§7.");
p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 10, 0.2, 0.2, 0.2, 0.02);
} else {
target.addScoreboardTag("as_lookat");
p.sendMessage(prefix + "§a§l[+] §7Blickkontakt §aaktiviert§7.");
p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 15, 0.4, 0.4, 0.4, 0.05);
}
break;
case "addplayer":
if (args.length < 2) return sendHelp(p, prefix);
String pCmd = buildString(args, 1);
target.addScoreboardTag("ascmd:player:" + pCmd);
p.sendMessage(prefix + "§aBefehl (Player) gespeichert: §e/" + pCmd);
if (args.length < 4) return sendHelp(p, prefix);
String s1P = args[1];
String s2P = args[2];
String pCmd = buildString(args, 3);
target.addScoreboardTag("ascmd:" + s1P + ":" + s2P + ":player:" + pCmd);
p.sendMessage(prefix + "§aBefehl (Player) auf Slot " + s1P + "/" + s2P + " gespeichert.");
break;
case "addconsole":
if (args.length < 2) return sendHelp(p, prefix);
String cCmd = buildString(args, 1);
target.addScoreboardTag("ascmd:console:" + cCmd);
p.sendMessage(prefix + "§aBefehl (Konsole) gespeichert: §e" + cCmd);
if (args.length < 4) return sendHelp(p, prefix);
String s1C = args[1];
String s2C = args[2];
String cCmd = buildString(args, 3);
target.addScoreboardTag("ascmd:" + s1C + ":" + s2C + ":console:" + cCmd);
p.sendMessage(prefix + "§aBefehl (Konsole) auf Slot " + s1C + "/" + s2C + " gespeichert.");
break;
case "remove":
@@ -117,18 +160,12 @@ public class ArmorStandCommand implements CommandExecutor {
return true;
}
/**
* Sucht den am besten passenden ArmorStand.
* Priorität 1: Der ArmorStand, den der Spieler direkt ansieht.
* Priorität 2: Der absolut nächste ArmorStand im Umkreis.
*/
private ArmorStand getBestTargetArmorStand(Player p) {
ArmorStand best = null;
double bestDot = 0.95; // Schwellenwert für das "Anschauen"
double bestDot = 0.95;
for (Entity e : p.getNearbyEntities(8, 8, 8)) {
if (e instanceof ArmorStand as) {
// Berechne, ob der Spieler in Richtung des ArmorStands schaut
double dot = p.getLocation().getDirection().dot(
as.getLocation().toVector().subtract(p.getLocation().toVector()).normalize()
);
@@ -140,7 +177,6 @@ public class ArmorStandCommand implements CommandExecutor {
}
}
// Falls nichts aktiv angeschaut wird, nimm einfach den nächsten im 4-Block-Radius
if (best == null) {
for (Entity e : p.getNearbyEntities(4, 4, 4)) {
if (e instanceof ArmorStand as) {
@@ -163,12 +199,12 @@ public class ArmorStandCommand implements CommandExecutor {
private boolean sendHelp(Player p, String prefix) {
p.sendMessage(" ");
p.sendMessage("§8§m-----------§r " + prefix + "§8§m-----------");
p.sendMessage("§e/astools §7- Editor öffnen");
p.sendMessage("§b/astools dynamic §7- Wetter/Zeit Logik umschalten");
p.sendMessage("§e/astools addplayer <cmd> §7- Befehl als Spieler");
p.sendMessage("§e/astools addconsole <cmd> §7- Befehl via Konsole");
p.sendMessage("§e/astools §7- Editor GUI öffnen");
p.sendMessage("§b/astools dynamic §7- Näherung/Zeit Logik");
p.sendMessage("§b/astools lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/astools addplayer <S1> <S2> <cmd> §7- Befehl (Spieler)");
p.sendMessage("§e/astools addconsole <S1> <S2> <cmd> §7- Befehl (Konsole)");
p.sendMessage("§e/astools remove §7- Befehle löschen");
p.sendMessage("§e/astools reload §7- Konfig neu laden");
p.sendMessage("§8§m---------------------------------------");
return true;
}

View File

@@ -0,0 +1,113 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.EulerAngle;
import org.bukkit.util.Vector;
public class ArmorStandLookAtModule {
public ArmorStandLookAtModule() {
startRotationTask();
}
private void startRotationTask() {
new BukkitRunnable() {
@Override
public void run() {
// Wir gehen alle Welten durch, um markierte ArmorStands zu finden
for (World world : Bukkit.getWorlds()) {
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
// Nur ArmorStands mit dem Tag "as_lookat"
if (as.getScoreboardTags().contains("as_lookat")) {
// Suche den nächsten Spieler im Umkreis von 7 Blöcken
Player nearest = getNearestPlayer(as, 7.0);
if (nearest != null) {
// Spieler gefunden -> Kopf zum Spieler drehen
updateLookAt(as, nearest);
} else {
// Kein Spieler da -> Kopf sanft auf 0,0,0 zurücksetzen
resetHeadPose(as);
}
}
}
}
}
}.runTaskTimer(NexusLobby.getInstance(), 0L, 1L);
}
private void updateLookAt(ArmorStand as, Player player) {
Location asLoc = as.getEyeLocation();
Location target = player.getEyeLocation();
Vector direction = target.toVector().subtract(asLoc.toVector()).normalize();
// Ziel-Winkel aus Vektor berechnen
double targetYaw = Math.toDegrees(Math.atan2(-direction.getX(), direction.getZ()));
double targetPitch = Math.toDegrees(Math.asin(direction.getY()));
// Relativer Yaw zum Körper-Yaw
double relativeYaw = targetYaw - as.getLocation().getYaw();
relativeYaw = ((relativeYaw + 180) % 360 + 360) % 360 - 180;
double yawRad = Math.toRadians(relativeYaw);
double pitchRad = Math.toRadians(-targetPitch);
// Begrenzung (Nacken-Schutz)
if (yawRad > 1.2) yawRad = 1.2;
if (yawRad < -1.2) yawRad = -1.2;
if (pitchRad > 0.8) pitchRad = 0.8;
if (pitchRad < -0.8) pitchRad = -0.8;
applySmoothPose(as, pitchRad, yawRad);
}
private void resetHeadPose(ArmorStand as) {
EulerAngle current = as.getHeadPose();
// Wenn der Kopf schon (fast) gerade ist, nichts tun
if (Math.abs(current.getX()) < 0.01 && Math.abs(current.getY()) < 0.01) {
if (current.getX() != 0 || current.getY() != 0) {
as.setHeadPose(new EulerAngle(0, 0, 0));
}
return;
}
// Ziel: 0, 0, 0 (Geradeaus schauen)
applySmoothPose(as, 0, 0);
}
private void applySmoothPose(ArmorStand as, double pitchRad, double yawRad) {
EulerAngle current = as.getHeadPose();
double lerp = 0.15; // Geschmeidigkeit
double finalPitch = current.getX() + (pitchRad - current.getX()) * lerp;
double finalYaw = current.getY() + (yawRad - current.getY()) * lerp;
as.setHeadPose(new EulerAngle(finalPitch, finalYaw, 0));
}
private Player getNearestPlayer(ArmorStand as, double range) {
Player nearest = null;
double dSquared = range * range;
for (Player p : as.getWorld().getPlayers()) {
// Falls Spieler im Vanish/Spectator ist, ignorieren (optional)
if (p.getGameMode().name().equals("SPECTATOR")) continue;
double dist = p.getLocation().distanceSquared(as.getLocation());
if (dist < dSquared) {
dSquared = dist;
nearest = p;
}
}
return nearest;
}
}

View File

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

View File

@@ -0,0 +1,135 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import de.nexuslobby.modules.servers.ServerChecker;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.World;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* ArmorStandStatusModule - Optimierte Statusanzeige für Java & Bedrock.
* - Schaltet zwischen Servername und "Offline" um.
* - Anti-Blink-Logik für Java-Spieler.
* - Force-Refresh-Logik für Bedrock-Spieler (Geyser).
*/
public class ArmorStandStatusModule implements Module {
private final Map<UUID, Boolean> lastStatus = new HashMap<>();
private final Map<String, Integer> failCount = new HashMap<>();
// Zähler für den Force-Refresh (Bedrock-Sicherheit)
private int refreshTicks = 0;
@Override
public String getName() {
return "ArmorStandStatus";
}
@Override
public void onEnable() {
// Alle 10 Sekunden (200 Ticks)
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
refreshTicks++;
updateAllServerArmorStands();
}, 100L, 200L);
}
@Override
public void onDisable() {
lastStatus.clear();
failCount.clear();
}
private void updateAllServerArmorStands() {
// Alle 6 Durchgänge (ca. jede Minute) erzwingen wir ein Update für Bedrock
boolean forceRefresh = (refreshTicks >= 6);
if (forceRefresh) refreshTicks = 0;
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntitiesByClass(ArmorStand.class)) {
if (entity instanceof ArmorStand as) {
checkAndRefreshStatus(as, forceRefresh);
}
}
}
}
private void checkAndRefreshStatus(ArmorStand as, boolean forceRefresh) {
String serverName = null;
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("ascmd:")) {
if (tag.contains(":bungee:")) {
String[] parts = tag.split(":");
if (parts.length >= 5) {
serverName = parts[4].toLowerCase();
} else if (tag.startsWith("ascmd:bungee:")) {
serverName = tag.replace("ascmd:bungee:", "").toLowerCase();
}
}
if (serverName != null) break;
}
}
if (serverName == null) return;
final String finalServerName = serverName;
String ip = NexusLobby.getInstance().getConfig().getString("servers." + serverName + ".ip", "127.0.0.1");
int port = NexusLobby.getInstance().getConfig().getInt("servers." + serverName + ".port", 25565);
ServerChecker.isOnline(ip, port).thenAccept(isOnline -> {
Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> {
// Toleranz-Logik gegen kurzes Flackern
if (isOnline) {
failCount.put(finalServerName, 0);
} else {
int fails = failCount.getOrDefault(finalServerName, 0) + 1;
failCount.put(finalServerName, fails);
if (fails < 2) return;
}
String originalDisplayName = getOriginalName(as);
if (originalDisplayName == null) return;
// Status-Check: Hat sich etwas geändert?
Boolean lastKnown = lastStatus.get(as.getUniqueId());
// Nur wenn Status neu ODER Force-Refresh (für Bedrock) aktiv ist
if (!forceRefresh && lastKnown != null && lastKnown == isOnline) {
return;
}
// Namen setzen
if (isOnline) {
String translatedName = ChatColor.translateAlternateColorCodes('&', originalDisplayName);
as.setCustomName(translatedName);
} else {
as.setCustomName("§cOffline");
}
// Bedrock-Fix: Sichtbarkeit explizit triggern
as.setCustomNameVisible(true);
// Status speichern
lastStatus.put(as.getUniqueId(), isOnline);
});
});
}
private String getOriginalName(ArmorStand as) {
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("as_displayname:")) {
return tag.replace("as_displayname:", "").replace("§§", ":");
}
}
return null;
}
}

View File

@@ -6,11 +6,12 @@ import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import de.nexuslobby.NexusLobby;
import java.util.ArrayList;
import java.util.List;
public enum ArmorStandTool {
// --- HAUPTMENÜ ATTRIBUTE ---
INVIS(Material.GLASS_PANE, 10),
ARMS(Material.STICK, 11),
BASE(Material.STONE_SLAB, 12),
@@ -19,10 +20,11 @@ public enum ArmorStandTool {
INVUL(Material.BEDROCK, 15),
SET_NAME(Material.NAME_TAG, 16),
// Zentrales Item zum Öffnen des Pose-Editors
// Neuer Button für Gespräche
CONV_SETUP(Material.WRITABLE_BOOK, 17),
OPEN_POSE_EDITOR(Material.ARMOR_STAND, 31),
// --- DIESE TEILE WANDERN IN DAS UNTERMENÜ (isForGui = false) ---
HEAD_ROT(Material.PLAYER_HEAD, -1),
BODY_ROT(Material.IRON_CHESTPLATE, -1),
L_ARM_ROT(Material.STICK, -1),
@@ -50,16 +52,28 @@ public enum ArmorStandTool {
case INVUL -> as.setInvulnerable(!as.isInvulnerable());
case SET_NAME -> {
p.closeInventory();
p.sendMessage("§6§lHologramm-Editor: §7Nutze §e/nexuscmd name <Text>");
p.sendMessage("§8[§6Nexus§8] §7Nutze §e/nexuscmd name <Text>");
AST.selectedArmorStand.put(p.getUniqueId(), as);
}
case CONV_SETUP -> {
// Automatisches Markieren via GUI
if (!p.hasMetadata("conv_npc1")) {
p.setMetadata("conv_npc1", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString()));
p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 1 markiert.");
} else {
p.setMetadata("conv_npc2", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString()));
p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 2 markiert.");
p.sendMessage("§8[§6Nexus§8] §7Nutze nun §e/nexuscmd conv start <ID>");
}
p.closeInventory();
}
case REMOVE -> {
as.remove();
p.closeInventory();
p.sendMessage("§cNexus ArmorStand entfernt.");
}
case OPEN_POSE_EDITOR -> ArmorStandPoseGUI.openPartSelectionMenu(p, as);
default -> {} // Pose-Detailklicks werden direkt im PoseGUI/Listener behandelt
default -> {}
}
}
@@ -70,8 +84,12 @@ public enum ArmorStandTool {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
String name = (this == OPEN_POSE_EDITOR) ? "§a§lPose Editor öffnen" : "§6" + this.name().replace("_", " ");
meta.setDisplayName(name);
String displayName = switch (this) {
case OPEN_POSE_EDITOR -> "§a§lPose Editor öffnen";
case CONV_SETUP -> "§d§lGesprächs-Setup";
default -> "§6" + this.name().replace("_", " ");
};
meta.setDisplayName(displayName);
List<String> lore = new ArrayList<>();
lore.add("§7Klicken zum Verwalten");
meta.setLore(lore);
@@ -83,8 +101,8 @@ public enum ArmorStandTool {
public static ArmorStandTool get(ItemStack item) {
if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return null;
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace(" ", "_");
// Spezielles Mapping für den Pose-Editor-Button im Listener
if (name.equals("Pose_Editor_öffnen")) return OPEN_POSE_EDITOR;
if (name.equals("Gesprächs-Setup")) return CONV_SETUP;
try { return valueOf(name); } catch (Exception e) { return null; }
}
}

View File

@@ -0,0 +1,280 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.*;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class ConversationManager {
private final NexusLobby plugin;
private File file;
private FileConfiguration config;
// Verhindert Mehrfach-Gespräche und regelt die 60s Pause
private final Set<UUID> activeSpeakers = new HashSet<>();
public ConversationManager(NexusLobby plugin) {
this.plugin = plugin;
setupFile();
clearHangingBubbles();
}
/**
* Gibt alle verfügbaren IDs zurück.
*/
public List<String> getConversationIds() {
if (config == null || !config.contains("conversations")) {
return new ArrayList<>();
}
ConfigurationSection section = config.getConfigurationSection("conversations");
if (section == null) return new ArrayList<>();
return new ArrayList<>(section.getKeys(false));
}
/**
* Entfernt alle verwaisten Sprechblasen in allen Welten.
*/
public void clearHangingBubbles() {
int count = 0;
for (World world : Bukkit.getWorlds()) {
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
if (as.getScoreboardTags().contains("nexus_bubble") ||
(!as.isVisible() && as.isMarker() && as.getCustomName() != null)) {
as.remove();
count++;
}
}
}
if (count > 0) {
Bukkit.getLogger().info("[NexusLobby] Cleanup: " + count + " verwaiste Sprechblasen entfernt.");
}
}
public void setupFile() {
this.file = new File(plugin.getDataFolder(), "conversations.yml");
if (!file.exists()) {
plugin.saveResource("conversations.yml", false);
}
this.config = YamlConfiguration.loadConfiguration(file);
}
/**
* Ermittelt die Tageszeit für die YAML-Pfade.
*/
private String getDaytimeSuffix() {
if (Bukkit.getWorlds().isEmpty()) return "mittags";
World world = Bukkit.getWorlds().get(0);
long time = world.getTime();
if (time >= 0 && time < 12000) return "morgens";
if (time >= 12000 && time < 13000) return "abends";
if (time >= 13000 && time < 23000) return "nacht";
return "mittags";
}
/**
* Pool-Logik für zufällige Dialog-Varianten oder zeitbasierte IDs.
*/
private String resolveDialogKey(String key) {
ConfigurationSection section = config.getConfigurationSection("conversations");
if (section == null) return key;
String daytimeKey = key + "_" + getDaytimeSuffix();
if (section.contains(daytimeKey)) return daytimeKey;
List<String> pool = section.getKeys(false).stream()
.filter(s -> s.startsWith(key))
.collect(Collectors.toList());
if (pool.isEmpty()) return key;
return pool.get(new Random().nextInt(pool.size()));
}
/**
* Automatische Prüfung für NPCs in Spieler-Nähe (Unterstützt bis zu 4 Partner).
*/
public void startAllAutomatedConversations() {
ConfigurationSection links = config.getConfigurationSection("links");
if (links == null) return;
for (String npc1String : links.getKeys(false)) {
try {
UUID id1 = UUID.fromString(npc1String);
if (activeSpeakers.contains(id1)) continue;
ConfigurationSection npcLink = links.getConfigurationSection(npc1String);
if (npcLink == null) continue;
String dialogId = npcLink.getString("dialog");
List<UUID> partners = new ArrayList<>();
partners.add(id1); // Der Starter ist immer Teilnehmer 0
// Partner 1 bis 3 einsammeln (Max 4 Teilnehmer insgesamt)
if (npcLink.contains("partner")) partners.add(UUID.fromString(npcLink.getString("partner")));
if (npcLink.contains("partner2")) partners.add(UUID.fromString(npcLink.getString("partner2")));
if (npcLink.contains("partner3")) partners.add(UUID.fromString(npcLink.getString("partner3")));
Entity starter = Bukkit.getEntity(id1);
if (starter instanceof ArmorStand as1) {
if (as1.getNearbyEntities(15, 15, 15).stream().anyMatch(e -> e instanceof Player)) {
String finalDialogId = resolveDialogKey(dialogId);
playConversationGroup(partners, finalDialogId);
}
}
} catch (Exception ignored) {}
}
}
/**
* Neue Gruppen-Logik: Spielt Dialoge für 2, 3 oder 4 Teilnehmer ab.
*/
public void playConversationGroup(List<UUID> participants, String key) {
String daytime = getDaytimeSuffix();
// Pfad-Ermittlung
String path = "conversations." + key + "." + daytime;
if (!config.contains(path + ".dialogue")) {
path = "conversations." + key + ".mittags";
}
if (!config.contains(path + ".dialogue")) {
path = "conversations." + key;
}
if (!config.contains(path + ".dialogue")) return;
List<String> lines = config.getStringList(path + ".dialogue");
if (lines.isEmpty()) return;
// Alle Teilnehmer blockieren
for (UUID id : participants) activeSpeakers.add(id);
new BukkitRunnable() {
int step = 0;
@Override
public void run() {
if (step >= lines.size()) {
// 60 SEKUNDEN COOLDOWN
Bukkit.getScheduler().runTaskLater(plugin, () -> {
for (UUID id : participants) activeSpeakers.remove(id);
}, 1200L);
this.cancel();
return;
}
// Rotations-Logik: Teilnehmer 0 -> 1 -> 2 -> 3 -> 0 ...
UUID speakerUUID = participants.get(step % participants.size());
Entity entity = Bukkit.getEntity(speakerUUID);
if (entity instanceof ArmorStand as) {
playDynamicSound(as);
showBubble(as, lines.get(step));
} else {
for (UUID id : participants) activeSpeakers.remove(id);
this.cancel();
return;
}
step++;
}
}.runTaskTimer(plugin, 0L, 90L); // 4.5s Intervall
}
/**
* Spielt einen Sound ab, dessen Pitch (Tonhöhe) zum Namen des ArmorStands passt.
*/
private void playDynamicSound(ArmorStand as) {
float pitch = 1.0f;
String name = as.getCustomName() != null ? as.getCustomName() : "";
if (name.contains("Timmy") || name.contains("Mia") || name.contains("Lotte")) {
pitch = 1.5f + (new Random().nextFloat() * 0.3f); // Hoch (Kind/Frau)
} else if (name.contains("Schmidt") || name.contains("Vater") || name.contains("Trainer")) {
pitch = 0.6f + (new Random().nextFloat() * 0.2f); // Tief (Mann)
} else {
pitch = 0.9f + (new Random().nextFloat() * 0.4f); // Neutral
}
as.getWorld().playSound(as.getLocation(), Sound.ENTITY_VILLAGER_AMBIENT, 0.5f, pitch);
}
/**
* Erzeugt die Sprechblase und einen visuellen Effekt (Partikel).
*/
public void showBubble(ArmorStand as, String text) {
Location loc = as.getEyeLocation().add(0, 0.8, 0);
// Kleiner Partikel-Effekt ("Sprechwolke")
as.getWorld().spawnParticle(Particle.CLOUD, loc, 3, 0.1, 0.1, 0.1, 0.02);
ArmorStand bubble = as.getWorld().spawn(loc, ArmorStand.class, s -> {
s.setMarker(true);
s.setVisible(false);
s.setGravity(false);
s.setCustomName(ChatColorTranslate(text));
s.setCustomNameVisible(true);
s.setInvulnerable(true);
s.addScoreboardTag("nexus_bubble");
});
Bukkit.getScheduler().runTaskLater(plugin, bubble::remove, 80L); // 4s Sichtbarkeit
}
// --- Legacy Support & Config Methoden ---
public void playConversation(UUID id1, UUID id2, String key) {
playConversationGroup(Arrays.asList(id1, id2), key);
}
/**
* Neue erweiterte Speicher-Methode für bis zu 4 Partner.
*/
public void saveLinkExtended(UUID id1, UUID id2, UUID id3, UUID id4, String dialogId) {
String path = "links." + id1.toString();
config.set(path + ".dialog", dialogId);
config.set(path + ".partner", id2.toString());
if (id3 != null) config.set(path + ".partner2", id3.toString());
if (id4 != null) config.set(path + ".partner3", id4.toString());
saveConfig();
}
public void saveLink(UUID id1, UUID id2, String dialogId) {
saveLinkExtended(id1, id2, null, null, dialogId);
}
public void removeLink(UUID id) {
config.set("links." + id.toString(), null);
saveConfig();
}
private String ChatColorTranslate(String text) {
if (text == null) return "";
return text.replace("&", "§");
}
private void saveConfig() {
try {
config.save(file);
} catch (IOException e) {
Bukkit.getLogger().severe("Fehler beim Speichern der Conversations: " + e.getMessage());
}
}
public void reload() {
setupFile();
activeSpeakers.clear();
clearHangingBubbles();
}
}

View File

@@ -0,0 +1,397 @@
package de.nexuslobby.modules.ball;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.util.Vector;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import java.util.Objects;
public class SoccerModule implements Module, Listener, CommandExecutor {
// Ball Konstanten
private static final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
private static final String BALL_TAG = "nexusball_entity";
private static final String BALL_NAME = "NexusBall";
// Physik Konstanten
private static final double DRIBBLE_DETECTION_RADIUS = 0.7;
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;
// Particle Konstanten
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;
private ArmorStand ball;
private Location spawnLocation;
private long lastMoveTime;
@Override
public String getName() { return "Soccer"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("nexuslobby") != null) {
Objects.requireNonNull(NexusLobby.getInstance().getCommand("nexuslobby")).setExecutor(this);
}
loadConfigLocation();
// Optimiertes Cleanup-System: 3 Phasen statt 6
removeAllOldBalls();
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
removeAllOldBalls();
spawnBall();
}, 40L); // Nach 2 Sekunden
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBalls, 100L); // Finaler Check nach 5 Sekunden
// Haupt-Physik & Anti-Duplikat System
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
// Anti-Duplikat-Check (optimiert: nur in Ball-Welt)
if (ball != null && ball.isValid()) {
removeDuplicateBalls();
}
if (ball == null || !ball.isValid()) return;
Vector vel = ball.getVelocity();
double speed = vel.length();
handleWallBounce(vel);
handleParticles(speed);
handleDribbling();
// Automatischer Respawn bei Inaktivität oder Void
long respawnDelayMs = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000;
if (System.currentTimeMillis() - lastMoveTime > respawnDelayMs || ball.getLocation().getY() < VOID_THRESHOLD) {
respawnBall();
}
}, 1L, 1L);
}
/**
* Optimierte Dribbel-Logik: Ball folgt nahen Spielern
*/
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 p) {
Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector());
if (direction.lengthSquared() > 0) {
direction.normalize();
direction.setY(DRIBBLE_LIFT);
ball.setVelocity(direction.multiply(DRIBBLE_FORCE));
lastMoveTime = System.currentTimeMillis();
}
}
}
}
/**
* Entfernt Duplikate in der Umgebung des aktiven Balls (Performance-optimiert)
*/
private void removeDuplicateBalls() {
if (ball == null || !ball.isValid() || ball.getLocation().getWorld() == null) return;
for (Entity entity : ball.getLocation().getWorld().getNearbyEntities(ball.getLocation(), 50, 50, 50)) {
if (entity instanceof ArmorStand stand && isBallEntity(stand)) {
if (!stand.getUniqueId().equals(ball.getUniqueId())) {
stand.remove();
}
}
}
}
/**
* Entfernt alle Ball-Entities (nur in relevanter Welt wenn Spawn gesetzt)
*/
private void removeAllOldBalls() {
int removed = 0;
// Wenn Spawn-Location gesetzt ist, nur diese Welt durchsuchen (Performance!)
if (spawnLocation != null && spawnLocation.getWorld() != null) {
removed = cleanupBallsInWorld(spawnLocation.getWorld());
} else {
// Sonst alle Welten durchsuchen
for (World world : Bukkit.getWorlds()) {
removed += cleanupBallsInWorld(world);
}
}
if (removed > 0) {
Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt.");
}
}
/**
* Cleanup für eine einzelne Welt
*/
private int cleanupBallsInWorld(World world) {
int removed = 0;
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand stand && isBallEntity(stand)) {
if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) {
stand.remove();
removed++;
}
}
}
return removed;
}
/**
* Prüft ob ein ArmorStand ein Ball ist (Mehrere Erkennungsmethoden)
*/
private boolean isBallEntity(ArmorStand stand) {
// Methode 1: Tag-basiert (primär)
if (stand.getScoreboardTags().contains(BALL_TAG)) {
return true;
}
// Methode 2: Name-basiert
if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) {
return true;
}
// Methode 3: Profil-basiert (Kopf-Textur)
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) {
if (BALL_NAME.equals(meta.getOwnerProfile().getName())) {
return true;
}
}
}
}
// Methode 4: Eigenschaften-basiert (nur wenn Spawn gesetzt)
if (spawnLocation != null && spawnLocation.getWorld() != null &&
stand.getWorld().equals(spawnLocation.getWorld()) &&
stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) {
double distance = stand.getLocation().distance(spawnLocation);
if (distance < CLEANUP_RADIUS && stand.getEquipment() != null &&
stand.getEquipment().getHelmet() != null &&
stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) {
return true;
}
}
return false;
}
private void handleWallBounce(Vector vel) {
if (vel.lengthSquared() < 0.001) return;
Location loc = ball.getLocation();
Block nextX = loc.clone().add(vel.getX() * WALL_CHECK_DISTANCE, 0.5, 0).getBlock();
Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * WALL_CHECK_DISTANCE).getBlock();
boolean bounced = false;
if (nextX.getType().isSolid()) {
vel.setX(-vel.getX() * WALL_BOUNCE_DAMPING);
bounced = true;
}
if (nextZ.getType().isSolid()) {
vel.setZ(-vel.getZ() * WALL_BOUNCE_DAMPING);
bounced = true;
}
if (bounced) {
ball.setVelocity(vel);
ball.getWorld().playSound(ball.getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 1.3f);
}
}
private void handleParticles(double speed) {
if (speed < PARTICLE_SPEED_MIN) return;
Location loc = ball.getLocation().add(0, 0.2, 0);
World world = loc.getWorld();
if (world == null) return;
if (speed > PARTICLE_SPEED_HIGH) {
world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0);
} else if (speed > PARTICLE_SPEED_MEDIUM) {
world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08);
} else {
world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
}
}
private void spawnBall() {
if (spawnLocation == null || spawnLocation.getWorld() == null) {
Bukkit.getLogger().warning("[NexusLobby] Ball-Spawn-Location nicht gesetzt! Verwende /nexuslobby ball setspawn");
return;
}
if (ball != null && ball.isValid()) return;
Location spawnLoc = spawnLocation.clone();
ball = (ArmorStand) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.ARMOR_STAND);
ball.setInvisible(true);
ball.setGravity(true);
ball.setBasePlate(false);
ball.setSmall(true);
ball.setInvulnerable(false);
ball.setArms(false);
ball.setCustomNameVisible(false);
ball.setCustomName(BALL_NAME);
ball.setPersistent(false);
ball.addScoreboardTag(BALL_TAG);
ItemStack ballHead = getSoccerHead();
if (ball.getEquipment() != null) {
ball.getEquipment().setHelmet(ballHead);
}
lastMoveTime = System.currentTimeMillis();
}
private ItemStack getSoccerHead() {
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (meta == null) return head;
PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), BALL_NAME);
try {
profile.getTextures().setSkin(new URL(TEXTURE_URL));
} catch (MalformedURLException e) {
Bukkit.getLogger().warning("[NexusLobby] Ungültige Ball-Textur URL!");
}
meta.setOwnerProfile(profile);
head.setItemMeta(meta);
return head;
}
public void respawnBall() {
if (ball != null && ball.isValid()) {
ball.remove();
ball = null;
}
removeAllOldBalls();
spawnBall();
}
@EventHandler
public void onBallPunch(EntityDamageByEntityEvent event) {
if (ball == null || !event.getEntity().equals(ball)) return;
event.setCancelled(true);
if (event.getDamager() instanceof Player p) {
Vector shootDir = p.getLocation().getDirection();
if (shootDir.lengthSquared() > 0) {
shootDir.normalize().multiply(KICK_FORCE).setY(KICK_LIFT);
ball.setVelocity(shootDir);
ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f);
lastMoveTime = System.currentTimeMillis();
}
}
}
@EventHandler
public void onBallInteract(PlayerInteractAtEntityEvent event) {
if (ball != null && event.getRightClicked().equals(ball)) {
event.setCancelled(true);
}
}
private void loadConfigLocation() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
if (config.contains("ball.spawn")) {
spawnLocation = config.getLocation("ball.spawn");
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player p)) {
sender.sendMessage("§cNur Spieler können diesen Befehl ausführen.");
return true;
}
if (!p.hasPermission("nexuslobby.admin")) {
p.sendMessage("§cKeine Berechtigung!");
return true;
}
if (args.length >= 2 && args[0].equalsIgnoreCase("ball")) {
switch (args[1].toLowerCase()) {
case "setspawn" -> {
spawnLocation = p.getLocation();
NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation);
NexusLobby.getInstance().saveConfig();
respawnBall();
p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt und Ball respawnt!");
return true;
}
case "respawn" -> {
respawnBall();
p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt.");
return true;
}
case "remove" -> {
if (ball != null) {
ball.remove();
ball = null;
}
removeAllOldBalls();
p.sendMessage("§8[§6Nexus§8] §cBall entfernt und Cleanup durchgeführt.");
return true;
}
default -> {
p.sendMessage("§8[§6Nexus§8] §7Verwendung:");
p.sendMessage("§e/nexuslobby ball setspawn §7- Setzt den Ball-Spawn");
p.sendMessage("§e/nexuslobby ball respawn §7- Spawnt den Ball neu");
p.sendMessage("§e/nexuslobby ball remove §7- Entfernt den Ball");
return true;
}
}
}
return false;
}
@Override
public void onDisable() {
if (ball != null && ball.isValid()) {
ball.remove();
ball = null;
}
}
}

View File

@@ -0,0 +1,97 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class FreezeRay {
// Auf public gesetzt, damit das GadgetModule im PlayerMoveEvent darauf prüfen kann
public static final Set<UUID> frozenPlayers = new HashSet<>();
public static void shoot(Player shooter) {
Location start = shooter.getEyeLocation();
Vector direction = start.getDirection();
// Sound beim Schießen
shooter.getWorld().playSound(start, Sound.ENTITY_SNOW_GOLEM_SHOOT, 1.0f, 1.5f);
// Wir prüfen in 0.3er Schritten bis zu 15 Blöcke weit
for (double d = 0; d < 15; d += 0.3) {
Location point = start.clone().add(direction.clone().multiply(d));
// Partikel-Strahl sichtbar machen
point.getWorld().spawnParticle(Particle.SNOWFLAKE, point, 1, 0, 0, 0, 0);
// Prüfung auf Spieler im Umkreis von 0.8 Blöcken (etwas großzügiger)
for (Entity entity : point.getWorld().getNearbyEntities(point, 0.8, 0.8, 0.8)) {
if (entity instanceof Player target && target != shooter) {
applyFreeze(target);
return; // Stoppt den Strahl beim ersten Treffer
}
}
// Stoppe den Strahl, falls er eine Wand trifft
if (point.getBlock().getType().isSolid()) {
break;
}
}
}
private static void applyFreeze(Player target) {
if (frozenPlayers.contains(target.getUniqueId())) return;
frozenPlayers.add(target.getUniqueId());
// Fixiere die Position für den Stasis-Effekt
final Location freezeLocation = target.getLocation();
// Feedback für getroffenen Spieler
target.sendMessage("§8[§6Nexus§8] §bDu wurdest eingefroren!");
target.getWorld().playSound(target.getLocation(), Sound.BLOCK_GLASS_BREAK, 1.0f, 0.5f);
new org.bukkit.scheduler.BukkitRunnable() {
int ticks = 0;
@Override
public void run() {
// Sicherheitscheck: Ist der Spieler noch online?
if (!target.isOnline() || ticks >= 60) {
frozenPlayers.remove(target.getUniqueId());
this.cancel();
return;
}
// Stasis-Effekt: Bewegung auf 0 setzen und Position fixieren
target.setVelocity(new Vector(0, 0, 0));
// Alle 2 Ticks zurückteleportieren, falls er versucht zu laufen
// (Behält die Blickrichtung des Spielers bei)
Location current = target.getLocation();
if (current.getX() != freezeLocation.getX() || current.getZ() != freezeLocation.getZ()) {
freezeLocation.setYaw(current.getYaw());
freezeLocation.setPitch(current.getPitch());
target.teleport(freezeLocation);
}
// Optischer Käfig (Ring-Effekt)
Location loc = target.getLocation();
for (int i = 0; i < 8; i++) {
double angle = i * Math.PI / 4;
double x = Math.cos(angle) * 0.7;
double z = Math.sin(angle) * 0.7;
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 1, z), 1, 0, 0, 0, 0);
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 0.2, z), 1, 0, 0, 0, 0);
}
ticks += 2;
}
}.runTaskTimer(NexusLobby.getInstance(), 0L, 2L);
}
}

View File

@@ -3,15 +3,20 @@ package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
@@ -56,24 +61,45 @@ public class GadgetModule implements Module, Listener {
}, 1L, 1L);
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
ItemStack item = event.getItem();
if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return;
String name = item.getItemMeta().getDisplayName();
if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
if (name.equals("§b§lFreeze-Ray")) {
FreezeRay.shoot(event.getPlayer());
event.setCancelled(true);
} else if (name.equals("§6§lPaintball-Gun")) {
PaintballGun.shoot(event.getPlayer());
event.setCancelled(true);
} else if (name.equals("§c§lMeteorit")) {
MeteorStrike.launch(event.getPlayer());
event.setCancelled(true);
}
}
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (FreezeRay.frozenPlayers.contains(event.getPlayer().getUniqueId())) {
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getZ() != event.getTo().getZ()) {
event.setTo(event.getFrom().setDirection(event.getTo().getDirection()));
}
}
}
private void handleSpecialHatEffects(Player p) {
ItemStack hat = p.getInventory().getHelmet();
if (hat == null || hat.getType() == Material.AIR) return;
switch (hat.getType()) {
case CAMPFIRE:
p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02);
break;
case SPAWNER:
p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02);
break;
case SEA_LANTERN:
case BEACON:
p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 2.1, 0), 1, 0.1, 0.1, 0.1, 0.03);
break;
case ENCHANTING_TABLE:
p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5);
break;
case CAMPFIRE -> p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02);
case SPAWNER -> p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02);
case SEA_LANTERN, BEACON -> p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 2.1, 0), 1, 0.1, 0.1, 0.1, 0.03);
case ENCHANTING_TABLE -> p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5);
default -> {}
}
}
@@ -102,7 +128,6 @@ public class GadgetModule implements Module, Listener {
gui.setItem(14, createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?"));
gui.setItem(15, createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte"));
gui.setItem(16, createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!"));
gui.setItem(19, createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig..."));
gui.setItem(20, createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig"));
gui.setItem(21, createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land"));
@@ -110,7 +135,6 @@ public class GadgetModule implements Module, Listener {
gui.setItem(23, createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln"));
gui.setItem(24, createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf"));
gui.setItem(25, createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig"));
gui.setItem(28, createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast"));
gui.setItem(29, createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!"));
gui.setItem(30, createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern"));
@@ -162,9 +186,12 @@ public class GadgetModule implements Module, Listener {
private void openFunGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 27, FUN_TITLE);
fillEdges(gui);
gui.setItem(11, createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft!"));
gui.setItem(13, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz"));
gui.setItem(15, createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!"));
gui.setItem(10, createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft!"));
gui.setItem(11, createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Friere andere Spieler ein!"));
gui.setItem(12, createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Male die Lobby bunt aus!"));
gui.setItem(14, createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Lass es krachen!"));
gui.setItem(15, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz"));
gui.setItem(16, createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!"));
gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
player.openInventory(gui);
}
@@ -187,37 +214,36 @@ public class GadgetModule implements Module, Listener {
else if (item.getType() == Material.NETHER_STAR) openParticleGUI(player);
else if (item.getType() == Material.FIREWORK_ROCKET) openFunGUI(player);
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) {
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.closeInventory();
}
}
else if (title.equals(PET_TITLE)) {
} else if (title.equals(PET_TITLE)) {
if (item.getType() == Material.BONE) PetManager.spawnEntityPet(player, "WOLF");
else if (item.getType() == Material.CAT_SPAWN_EGG) PetManager.spawnEntityPet(player, "CAT");
else if (item.getType() == Material.PANDA_SPAWN_EGG) PetManager.spawnEntityPet(player, "PANDA");
player.sendMessage("§8[§6Nexus§8] §dDein Pet wurde gerufen!");
player.closeInventory();
}
else if (title.equals(BALLOON_TITLE)) {
} else if (title.equals(BALLOON_TITLE)) {
if (item.getType().toString().endsWith("_WOOL")) {
if (activeBalloons.containsKey(player.getUniqueId())) activeBalloons.get(player.getUniqueId()).remove();
activeBalloons.put(player.getUniqueId(), new Balloon(player, item.getType()));
player.sendMessage("§8[§6Nexus§8] §aBallon aktiviert!");
player.closeInventory();
}
}
else if (title.equals(PARTICLE_TITLE)) {
} else if (title.equals(PARTICLE_TITLE)) {
if (item.getType() == Material.POPPY) activeEffects.put(player.getUniqueId(), new ParticleEffect("hearts"));
else if (item.getType() == Material.BLAZE_POWDER) activeEffects.put(player.getUniqueId(), new ParticleEffect("flames"));
else if (item.getType() == Material.WATER_BUCKET) activeEffects.put(player.getUniqueId(), new ParticleEffect("cloud"));
player.sendMessage("§8[§6Nexus§8] §aPartikel aktiviert!");
player.closeInventory();
}
else if (title.equals(FUN_TITLE)) {
} else if (title.equals(FUN_TITLE)) {
if (item.getType() == Material.EGG) {
ChickenRain.start(player);
player.sendMessage("§8[§6Nexus§8] §fHühnerregen gestartet!");
@@ -225,6 +251,15 @@ public class GadgetModule implements Module, Listener {
} else if (item.getType() == Material.FISHING_ROD) {
player.getInventory().addItem(createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren"));
player.closeInventory();
} else if (item.getType() == Material.PACKED_ICE) {
player.getInventory().addItem(createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Rechtsklick zum Einfrieren"));
player.closeInventory();
} else if (item.getType() == Material.GOLDEN_HOE) {
player.getInventory().addItem(createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Rechtsklick zum Schießen"));
player.closeInventory();
} else if (item.getType() == Material.FIRE_CHARGE) {
player.getInventory().addItem(createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Rechtsklick zum Markieren"));
player.closeInventory();
} else if (item.getType() == Material.SHIELD) {
if (activeShields.contains(player.getUniqueId())) {
activeShields.remove(player.getUniqueId());
@@ -242,7 +277,8 @@ public class GadgetModule implements Module, Listener {
public void onFish(PlayerFishEvent event) {
Player player = event.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand();
if (item != null && 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.getHook() != null) {
GrapplingHook.pullPlayer(player, event.getHook().getLocation());
@@ -261,10 +297,14 @@ public class GadgetModule implements Module, Listener {
PetManager.removePet(player);
HatManager.removeHat(player);
player.getInventory().remove(Material.FISHING_ROD);
player.getInventory().remove(Material.PACKED_ICE);
player.getInventory().remove(Material.GOLDEN_HOE);
player.getInventory().remove(Material.FIRE_CHARGE);
player.sendMessage("§8[§6Nexus§8] §cAlle Gadgets abgelegt.");
}
private void fillEdges(Inventory inv) {
// Bedrock braucht einen Space, um den Namen korrekt anzuzeigen
ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ", null);
for (int i = 0; i < inv.getSize(); i++) {
if (i < 9 || i >= inv.getSize() - 9 || i % 9 == 0 || (i + 1) % 9 == 0) inv.setItem(i, glass);
@@ -276,11 +316,19 @@ public class GadgetModule implements Module, Listener {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
if (lore != null) {
List<String> l = new ArrayList<>();
l.add(lore);
// WICHTIG FÜR BEDROCK: Saubere ArrayList für Lore
List<String> l = new ArrayList<>();
if (lore != null && !lore.isEmpty()) {
l.add(ChatColor.translateAlternateColorCodes('&', lore));
meta.setLore(l);
}
// VERSTECKT ATTRIBUTE: Verhindert "+Armor" etc., was bei Bedrock die Lore überdeckt
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
item.setItemMeta(meta);
}
return item;

View File

@@ -0,0 +1,47 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class MeteorStrike {
public static void launch(Player shooter) {
Location target = null;
Location start = shooter.getEyeLocation();
Vector direction = start.getDirection();
for (double d = 0; d < 30; d += 0.5) {
Location point = start.clone().add(direction.clone().multiply(d));
if (point.getBlock().getType().isSolid()) {
target = point;
break;
}
}
if (target == null) return;
final Location finalTarget = target.clone().add(0, 0.5, 0);
finalTarget.getWorld().spawnParticle(Particle.FLAME, finalTarget, 20, 0.5, 0.1, 0.5, 0.05);
shooter.sendMessage("§8[§6Nexus§8] §cMeteorit im Anflug...");
org.bukkit.Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
// EXPLOSION_EMITTER ist der moderne Name für HUGE_EXPLOSION
finalTarget.getWorld().spawnParticle(Particle.EXPLOSION_EMITTER, finalTarget, 1);
finalTarget.getWorld().spawnParticle(Particle.LAVA, finalTarget, 30, 0.5, 0.5, 0.5, 0.1);
finalTarget.getWorld().playSound(finalTarget, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f);
for (Entity entity : finalTarget.getWorld().getNearbyEntities(finalTarget, 4, 4, 4)) {
if (entity instanceof Player p) {
Vector v = p.getLocation().toVector().subtract(finalTarget.toVector()).normalize().multiply(1.5).setY(0.5);
p.setVelocity(v);
p.sendMessage("§cBUMM!");
}
}
}, 30L);
}
}

View File

@@ -0,0 +1,89 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.util.Random;
public class PaintballGun {
private static final Random random = new Random();
// Wir nutzen jetzt Wolle für kräftigere Farben
private static final Material[] COLORS = {
Material.RED_WOOL, Material.BLUE_WOOL, Material.LIME_WOOL,
Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL,
Material.YELLOW_WOOL, Material.PURPLE_WOOL, Material.PINK_WOOL
};
public static void shoot(Player shooter) {
Location start = shooter.getEyeLocation();
Vector direction = start.getDirection();
Material randomColor = COLORS[random.nextInt(COLORS.length)];
shooter.getWorld().playSound(start, Sound.ENTITY_CHICKEN_EGG, 1.0f, 2.0f);
for (double d = 0; d < 25; d += 0.5) {
Location point = start.clone().add(direction.clone().multiply(d));
// Flug-Partikel (kleiner Rauch oder Schneeball)
point.getWorld().spawnParticle(Particle.ITEM_SNOWBALL, point, 1, 0, 0, 0, 0);
Block block = point.getBlock();
if (block.getType().isSolid()) {
impact(block, randomColor);
break;
}
}
}
private static void impact(Block centerBlock, Material color) {
Location centerLoc = centerBlock.getLocation();
centerLoc.getWorld().playSound(centerLoc, Sound.ENTITY_SLIME_SQUISH, 1.0f, 1.2f);
int radius = 2; // Radius der Farbkugel
// Wir gehen alle Blöcke im Würfel um den Einschlag durch
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
// Berechne Distanz für eine Kugelform (statt Würfel)
if (x * x + y * y + z * z <= radius * radius + 0.5) {
Block target = centerLoc.clone().add(x, y, z).getBlock();
// Nur solide Blöcke färben (keine Luft/Gras)
if (target.getType().isSolid()) {
applyColor(target, color);
}
}
}
}
}
}
private static void applyColor(Block block, Material color) {
Location loc = block.getLocation();
// Effekt-Partikel am Block
loc.getWorld().spawnParticle(Particle.BLOCK, loc.clone().add(0.5, 0.5, 0.5), 3, 0.1, 0.1, 0.1, color.createBlockData());
// Block-Änderung an alle senden
for (Player online : Bukkit.getOnlinePlayers()) {
online.sendBlockChange(loc, color.createBlockData());
}
// Nach 10 Sekunden zurücksetzen
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
for (Player online : Bukkit.getOnlinePlayers()) {
online.sendBlockChange(loc, block.getBlockData());
}
}, 400L);
}
}

View File

@@ -158,7 +158,7 @@ public class HologramModule implements Module, Listener {
config.save(file);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!");
e.printStackTrace();
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Hologramme: " + e.getMessage());
}
}

View File

@@ -69,7 +69,11 @@ public class IntroModule implements Module, Listener, CommandExecutor {
private void savePoints() {
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

View File

@@ -67,7 +67,7 @@ public class MapArtModule implements Module, CommandExecutor {
try {
storageFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
NexusLobby.getInstance().getLogger().severe("Fehler beim Erstellen der mapart.yml: " + e.getMessage());
}
}
storageConfig = YamlConfiguration.loadConfiguration(storageFile);
@@ -77,7 +77,7 @@ public class MapArtModule implements Module, CommandExecutor {
try {
storageConfig.save(storageFile);
} catch (IOException e) {
e.printStackTrace();
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der mapart.yml: " + e.getMessage());
}
}

View File

@@ -0,0 +1,113 @@
package de.nexuslobby.modules.parkour;
import de.nexuslobby.NexusLobby;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
public class ParkourListener implements Listener {
private final ParkourManager manager;
private final String NPC_TAG = "parkour_npc";
private final HashMap<UUID, Long> startCooldown = new HashMap<>();
public ParkourListener(ParkourManager manager) {
this.manager = manager;
}
/**
* Startet den Parkour per Rechtsklick auf den ArmorStand
*/
@EventHandler
public void onInteract(PlayerInteractAtEntityEvent event) {
if (!(event.getRightClicked() instanceof ArmorStand as)) return;
// Prüfen, ob der ArmorStand den richtigen Tag hat
if (as.getScoreboardTags().contains(NPC_TAG)) {
Player player = event.getPlayer();
// Abbrechen, wenn der Spieler schon im Parkour ist
if (manager.isIngame(player)) return;
// Cooldown-Check (3 Sekunden)
if (startCooldown.containsKey(player.getUniqueId())) {
if (System.currentTimeMillis() - startCooldown.get(player.getUniqueId()) < 3000) {
player.sendMessage("§cBitte warte einen Moment, bevor du erneut startest.");
return;
}
}
// Parkour starten
manager.startParkour(player, as.getLocation());
player.sendMessage("§8[§6Parkour§8] §eViel Erfolg! Erreiche das Ziel so schnell wie möglich.");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f);
// Event abbrechen, damit man keine Ausrüstung vom ArmorStand klaut
event.setCancelled(true);
}
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
// Performance: Nur berechnen, wenn ein voller Block gewechselt wurde
if (event.getFrom().getBlockX() == event.getTo().getBlockX() &&
event.getFrom().getBlockY() == event.getTo().getBlockY() &&
event.getFrom().getBlockZ() == event.getTo().getBlockZ()) return;
Player player = event.getPlayer();
if (!manager.isIngame(player)) return;
Location loc = player.getLocation();
// --- 1. ABSTURZ-CHECK ---
// Passe die Höhe '50' an deine Map an!
if (loc.getY() < 50) {
Location lastCp = manager.getCheckpoint(player);
if (lastCp != null) {
player.teleport(lastCp);
player.sendMessage("§8[§6Parkour§8] §cAbgestürzt! Zurück zum Checkpoint.");
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.5f, 1.0f);
}
return;
}
// --- 2. CHECKPOINT-CHECK ---
List<Location> cps = manager.getOrderedCheckpoints();
for (int i = 0; i < cps.size(); i++) {
if (isNearby(loc, cps.get(i))) {
manager.reachCheckpoint(player, i);
}
}
// --- 3. ZIEL-CHECK ---
Location finish = manager.getFinishLocation();
if (finish != null && isNearby(loc, finish)) {
manager.finishParkour(player);
// Nach dem Ziel setzen wir einen Cooldown
startCooldown.put(player.getUniqueId(), System.currentTimeMillis());
}
}
private boolean isNearby(Location playerLoc, Location targetLoc) {
if (playerLoc.getWorld() == null || !playerLoc.getWorld().equals(targetLoc.getWorld())) return false;
// 2.25 entspricht einem Radius von ca. 1.5 Blöcken
return playerLoc.distanceSquared(targetLoc) <= 2.25;
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
manager.stopParkour(event.getPlayer());
startCooldown.remove(uuid);
}
}

View File

@@ -0,0 +1,241 @@
package de.nexuslobby.modules.parkour;
import de.nexuslobby.NexusLobby;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class ParkourManager {
private final NexusLobby plugin;
private final File file;
private FileConfiguration config;
private final Map<UUID, Long> startTime = new HashMap<>();
private final Map<UUID, Location> lastCheckpointLoc = new HashMap<>();
private final Map<UUID, Integer> nextCheckpointIndex = new HashMap<>();
public ParkourManager(NexusLobby plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "parkour.yml");
loadConfig();
startParticleTask();
}
private void loadConfig() {
if (!file.exists()) {
file.getParentFile().mkdirs();
try {
file.createNewFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Erstellen der Parkour-Datei: " + e.getMessage());
}
}
config = YamlConfiguration.loadConfiguration(file);
}
public void setCheckpoint(Player player, Location loc) {
int nextId = 1;
if (config.contains("locations.checkpoints") && config.getConfigurationSection("locations.checkpoints") != null) {
nextId = config.getConfigurationSection("locations.checkpoints").getKeys(false).size() + 1;
}
addCheckpointLocation(String.valueOf(nextId), loc);
player.sendMessage("§8[§6Parkour§8] §7Checkpoint §e#" + nextId + " §7an deiner Position gesetzt.");
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1.0f, 1.5f);
}
/**
* Löscht die gesamte Strecke (Checkpoints und Ziel) und bricht aktuelle Läufe ab.
*/
public void removeAllPoints() {
config.set("locations.checkpoints", null);
config.set("locations.finish", null);
save();
// Alle Spieler aus dem Parkour werfen, damit keine Partikel zu gelöschten Zielen führen
for (UUID uuid : new HashSet<>(startTime.keySet())) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) {
stopParkour(p);
p.sendMessage("§8[§6Parkour§8] §cDie aktuelle Strecke wurde soeben gelöscht.");
}
}
}
public void startParkour(Player player, Location startLoc) {
if (startTime.containsKey(player.getUniqueId())) return;
startTime.put(player.getUniqueId(), System.currentTimeMillis());
lastCheckpointLoc.put(player.getUniqueId(), startLoc);
nextCheckpointIndex.put(player.getUniqueId(), 0);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 1.2f);
new BukkitRunnable() {
@Override
public void run() {
if (!isIngame(player) || !player.isOnline()) {
this.cancel();
return;
}
long diff = System.currentTimeMillis() - startTime.get(player.getUniqueId());
double sec = diff / 1000.0;
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
new TextComponent("§6⏱ Zeit: §e" + String.format("%.2f", sec) + "s §8| §bNächster Punkt: §7Partikel folgen"));
}
}.runTaskTimer(plugin, 0L, 1L);
}
private void startParticleTask() {
new BukkitRunnable() {
@Override
public void run() {
for (UUID uuid : startTime.keySet()) {
Player p = Bukkit.getPlayer(uuid);
if (p == null) continue;
int nextIdx = nextCheckpointIndex.getOrDefault(uuid, 0);
List<Location> cps = getOrderedCheckpoints();
if (nextIdx < cps.size()) {
Location nextCp = cps.get(nextIdx);
if (nextCp != null && p.getWorld().equals(nextCp.getWorld())) {
p.spawnParticle(Particle.SOUL_FIRE_FLAME, nextCp.clone().add(0, 0.5, 0), 5, 0.1, 0.3, 0.1, 0.02);
}
} else {
Location finish = getFinishLocation();
if (finish != null && p.getWorld().equals(finish.getWorld())) {
p.spawnParticle(Particle.HAPPY_VILLAGER, finish.clone().add(0, 0.5, 0), 8, 0.2, 0.5, 0.2, 0.02);
}
}
}
}
}.runTaskTimer(plugin, 0L, 6L);
}
public void reachCheckpoint(Player player, int reachedIndex) {
int currentNext = nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0);
if (reachedIndex == currentNext) {
List<Location> cps = getOrderedCheckpoints();
if (reachedIndex < cps.size()) {
lastCheckpointLoc.put(player.getUniqueId(), cps.get(reachedIndex));
nextCheckpointIndex.put(player.getUniqueId(), reachedIndex + 1);
player.sendMessage("§8[§6Parkour§8] §bCheckpoint #" + (reachedIndex + 1) + " erreicht!");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.5f);
}
}
}
public void finishParkour(Player player) {
if (!startTime.containsKey(player.getUniqueId())) return;
if (nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0) < getOrderedCheckpoints().size()) {
player.sendMessage("§cDu hast Checkpoints übersprungen! Folge den Partikeln.");
return;
}
long duration = System.currentTimeMillis() - startTime.get(player.getUniqueId());
double seconds = duration / 1000.0;
player.sendMessage("§8§m--------------------------------------");
player.sendMessage("§8[§6Parkour§8] §a§lZiel erreicht!");
player.sendMessage("§7Deine Zeit: §e" + String.format("%.2f", seconds) + "s");
player.sendMessage("§8§m--------------------------------------");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f);
saveBestTime(player, seconds);
stopParkour(player);
}
public void stopParkour(Player player) {
startTime.remove(player.getUniqueId());
lastCheckpointLoc.remove(player.getUniqueId());
nextCheckpointIndex.remove(player.getUniqueId());
}
public void setStartLocation(Location loc) { config.set("locations.start", loc); save(); }
public void setFinishLocation(Location loc) { config.set("locations.finish", loc); save(); }
public void addCheckpointLocation(String id, Location loc) { config.set("locations.checkpoints." + id, loc); save(); }
public Location getStartLocation() { return config.getLocation("locations.start"); }
public Location getFinishLocation() { return config.getLocation("locations.finish"); }
public List<Location> getOrderedCheckpoints() {
if (!config.contains("locations.checkpoints") || config.getConfigurationSection("locations.checkpoints") == null)
return new ArrayList<>();
return config.getConfigurationSection("locations.checkpoints").getKeys(false).stream()
.sorted(Comparator.comparingInt(Integer::parseInt))
.map(key -> config.getLocation("locations.checkpoints." + key))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private void save() {
try {
config.save(file);
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern der Parkour-Config: " + e.getMessage());
}
}
public void clearStats() {
config.set("besttimes", null);
config.set("names", null);
save();
}
public boolean isIngame(Player player) { return startTime.containsKey(player.getUniqueId()); }
public Location getCheckpoint(Player player) { return lastCheckpointLoc.get(player.getUniqueId()); }
private void saveBestTime(Player player, double time) {
String path = "besttimes." + player.getUniqueId();
double currentTime = config.getDouble(path, 99999.9);
if (time < currentTime) {
config.set(path, time);
config.set("names." + player.getUniqueId(), player.getName());
save();
player.sendMessage("§8[§6Parkour§8] §6§lNeuer Rekord! §7Du hast dich verbessert.");
}
}
public String getTopTen() {
if (!config.contains("besttimes") || config.getConfigurationSection("besttimes") == null)
return "§6§l🏆 TOP 10 PARKOUR 🏆\n§7Noch keine Rekorde.";
Map<String, Double> allTimes = new HashMap<>();
for (String uuidStr : config.getConfigurationSection("besttimes").getKeys(false)) {
allTimes.put(uuidStr, config.getDouble("besttimes." + uuidStr));
}
List<Map.Entry<String, Double>> sortedList = allTimes.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.limit(10)
.toList();
StringBuilder builder = new StringBuilder("§6§l🏆 TOP 10 PARKOUR 🏆");
int rank = 1;
for (Map.Entry<String, Double> entry : sortedList) {
String name = config.getString("names." + entry.getKey(), "Unbekannt");
builder.append("\n§e#").append(rank).append(" §f").append(name).append(" §8» §a").append(String.format("%.2f", entry.getValue())).append("s");
rank++;
}
return builder.toString();
}
}

View File

@@ -0,0 +1,194 @@
package de.nexuslobby.modules.player;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.List;
/**
* Modul zur Inspektion von Spielern in der Lobby.
* Zeigt detaillierte Statistiken in einer interaktiven GUI an.
*/
public class PlayerInspectModule implements Module, Listener {
@Override
public String getName() {
return "PlayerInspect";
}
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
}
@Override
public void onDisable() {}
@EventHandler
public void onPlayerInteract(PlayerInteractEntityEvent event) {
// Prüfen, ob ein Spieler rechtsgeklickt wurde
if (event.getRightClicked() instanceof Player target) {
Player viewer = event.getPlayer();
// GUI nur öffnen, wenn die Hand leer ist (verhindert Konflikte mit Items)
if (viewer.getInventory().getItemInMainHand().getType() == Material.AIR) {
openDetailedInspectGUI(viewer, target);
}
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
// Sicherstellen, dass es unser Statistik-Inventar ist anhand des Titels
if (event.getView().getTitle().contains("Statistiken")) {
event.setCancelled(true); // Verhindert, dass Items herausgenommen werden
ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() == Material.AIR) return;
// Logik für den Schließen-Button (Barriere)
if (clickedItem.getType() == Material.BARRIER) {
event.getWhoClicked().closeInventory();
}
}
}
public void openDetailedInspectGUI(Player viewer, Player target) {
// Titel mit Farbcodes
String title = "§8» §3Statistiken: §b" + target.getName();
Inventory gui = Bukkit.createInventory(null, 45, title);
// --- Design: Hintergrund mit Glasscheiben füllen ---
ItemStack separator = createSimpleItem(Material.GRAY_STAINED_GLASS_PANE, " ");
int[] borderSlots = {
0,1,2,3,4,5,6,7,8,
9,17,
18,26,
27,35,
36,37,38,39,41,42,43,44
};
for (int slot : borderSlots) gui.setItem(slot, separator);
// --- Kopf des Spielers mit LuckPerms Prefix via PAPI ---
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta headMeta = (SkullMeta) head.getItemMeta();
if (headMeta != null) {
headMeta.setOwningPlayer(target);
headMeta.setDisplayName("§e§l" + target.getName());
List<String> lore = new ArrayList<>();
lore.add("§8§m-----------------------");
// LuckPerms Prefix über PlaceholderAPI auslesen (falls vorhanden)
String prefix = "%luckperms_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
prefix = ChatColor.translateAlternateColorCodes('&', prefix);
lore.add("§7Rang: " + (prefix.isEmpty() ? "§fSpieler" : prefix));
lore.add("§7Level: §a" + target.getLevel());
lore.add("§7Status: §aOnline");
lore.add("§8§m-----------------------");
headMeta.setLore(lore);
head.setItemMeta(headMeta);
}
gui.setItem(4, head);
// --- Statistik Kategorie: KAMPF (Slot 19) ---
gui.setItem(19, createStatsItem(Material.DIAMOND_SWORD, "§c§lKampf-Statistiken",
"§8» §7Spieler-Kills: §f" + target.getStatistic(Statistic.PLAYER_KILLS),
"§8» §7Mob-Kills: §f" + target.getStatistic(Statistic.MOB_KILLS),
"§8» §7Tode gesamt: §f" + target.getStatistic(Statistic.DEATHS),
"§8» §7Schaden verursacht: §f" + (target.getStatistic(Statistic.DAMAGE_DEALT) / 10) + ""));
// --- Statistik Kategorie: ARBEIT (Slot 21) ---
// Optimierte Abfrage für wichtige Blöcke
int totalBlocks = target.getStatistic(Statistic.MINE_BLOCK, Material.STONE) +
target.getStatistic(Statistic.MINE_BLOCK, Material.DIRT) +
target.getStatistic(Statistic.MINE_BLOCK, Material.COBBLESTONE);
gui.setItem(21, createStatsItem(Material.IRON_PICKAXE, "§e§lHandwerk & Fleiß",
"§8» §7Blöcke (S/D/C): §f" + totalBlocks,
"§8» §7Items gedroppt: §f" + target.getStatistic(Statistic.DROP_COUNT),
"§8» §7Fische gefangen: §f" + target.getStatistic(Statistic.FISH_CAUGHT),
"§8» §7Glocken geläutet: §f" + target.getStatistic(Statistic.BELL_RING)));
// --- Statistik Kategorie: BEWEGUNG (Slot 23) ---
double walkKm = target.getStatistic(Statistic.WALK_ONE_CM) / 100000.0;
double flyKm = target.getStatistic(Statistic.FLY_ONE_CM) / 100000.0;
gui.setItem(23, createStatsItem(Material.GOLDEN_BOOTS, "§b§lReise-Statistiken",
"§8» §7Gelaufen: §f" + String.format("%.2f", walkKm) + " km",
"§8» §7Geflogen: §f" + String.format("%.2f", flyKm) + " km",
"§8» §7Sprünge: §f" + target.getStatistic(Statistic.JUMP)));
// --- Statistik Kategorie: ZEIT (Slot 25) ---
long ticks = target.getStatistic(Statistic.PLAY_ONE_MINUTE);
long hours = ticks / 72000;
long mins = (ticks % 72000) / 1200;
gui.setItem(25, createStatsItem(Material.CLOCK, "§a§lZeit-Statistiken",
"§8» §7Spielzeit: §f" + hours + " Std. " + mins + " Min.",
"§8» §7Letzter Tod vor: §f" + (target.getStatistic(Statistic.TIME_SINCE_DEATH) / 1200) + " Min.",
"§8» §7Tage auf Server: §f" + (target.getStatistic(Statistic.PLAY_ONE_MINUTE) / 1728000)));
// --- Schließen Button (Slot 40) ---
gui.setItem(40, createSimpleItem(Material.BARRIER, "§c§lMenü schließen"));
// Inventar für den Zuschauer öffnen
viewer.openInventory(gui);
}
/**
* Erstellt ein ItemStack mit Name und Lore-Zeilen inklusive einer Leerzeile am Anfang.
*/
private ItemStack createStatsItem(Material material, String displayName, String... loreLines) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(displayName);
List<String> lore = new ArrayList<>();
lore.add(" "); // Leerzeile für sauberes Design
for (String line : loreLines) {
lore.add(line);
}
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
}
/**
* Erstellt ein einfaches ItemStack ohne Lore für Designzwecke.
*/
private ItemStack createSimpleItem(Material material, String displayName) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(displayName);
item.setItemMeta(meta);
}
return item;
}
}

View File

@@ -46,6 +46,7 @@ public class PortalManager implements Module, Listener {
private Location borderMin;
private Location borderMax;
private boolean borderEnabled = false;
private String borderType;
public PortalManager(NexusLobby plugin) {
this.plugin = plugin;
@@ -77,17 +78,25 @@ public class PortalManager implements Module, Listener {
}
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");
this.borderType = plugin.getConfig().getString("worldborder.type", "SQUARE");
boolean enabled = plugin.getConfig().getBoolean("worldborder.enabled", false);
if (!enabled || !"SQUARE".equalsIgnoreCase(borderType)) {
this.borderEnabled = false;
return;
}
if (plugin.getConfig().contains("worldborder.pos1") && plugin.getConfig().contains("worldborder.pos2")) {
Location p1 = plugin.getConfig().getLocation("worldborder.pos1");
Location p2 = plugin.getConfig().getLocation("worldborder.pos2");
if (p1 != null && p2 != null) {
this.borderMin = getMinLocation(p1, p2);
this.borderMax = getMaxLocation(p1, p2);
this.borderEnabled = true;
return;
}
} else {
this.borderEnabled = false;
}
this.borderEnabled = false;
}
public Set<String> getPortalNames() {
@@ -221,8 +230,7 @@ public class PortalManager implements Module, Listener {
try {
config.save(new java.io.File(plugin.getDataFolder(), "portals.yml"));
} catch (IOException e) {
plugin.getLogger().severe("Konnte portals.yml nicht speichern!");
e.printStackTrace();
plugin.getLogger().severe("Fehler beim Speichern der portals.yml: " + e.getMessage());
}
}
@@ -402,7 +410,7 @@ public class PortalManager implements Module, Listener {
out.writeUTF(serverName);
player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray());
} catch (IOException e) {
e.printStackTrace();
plugin.getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + e.getMessage());
}
}

View File

@@ -0,0 +1,135 @@
package de.nexuslobby.modules.servers;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.entity.ArmorStand;
import org.bukkit.scheduler.BukkitTask;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.CompletableFuture;
public class ServerChecker {
private static BukkitTask globalCheckerTask = null;
/**
* Prüft asynchron, ob ein Server unter der angegebenen IP und Port erreichbar ist.
*/
public static CompletableFuture<Boolean> isOnline(String ip, int port) {
return CompletableFuture.supplyAsync(() -> {
try (Socket socket = new Socket()) {
// 500ms Timeout für stabilere Erkennung
socket.connect(new InetSocketAddress(ip, port), 500);
return true;
} catch (IOException e) {
return false;
}
});
}
/**
* Startet den Scheduler, der alle ArmorStands in allen Welten regelmäßig prüft.
*/
public static void startGlobalChecker() {
// Cancel alten Task falls vorhanden
stopGlobalChecker();
// WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen
globalCheckerTask = Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
for (World world : Bukkit.getWorlds()) {
// Das Abrufen der Entities muss auf dem Main-Thread passieren
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
checkAndUpdateArmorStand(as);
}
}
}, 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.
*/
private static void checkAndUpdateArmorStand(ArmorStand as) {
for (String tag : as.getScoreboardTags()) {
// Erwartetes Format: ascmd:slot1:slot2:type:command
if (tag.startsWith("ascmd:")) {
String[] parts = tag.split(":");
if (parts.length < 5) continue;
String type = parts[3].toLowerCase();
if (!type.equals("bungee")) continue;
String serverName = parts[4];
// Suche den passenden Key in der Config
String configKey = null;
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) {
for (String key : NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false)) {
if (key.equalsIgnoreCase(serverName)) {
configKey = key;
break;
}
}
}
if (configKey == null) continue;
String ip = NexusLobby.getInstance().getConfig().getString("servers." + configKey + ".ip");
int port = NexusLobby.getInstance().getConfig().getInt("servers." + configKey + ".port");
if (ip == null) continue;
// Der Ping selbst läuft asynchron weg vom Main-Thread
isOnline(ip, port).thenAccept(online -> {
// Zurück zum Main-Thread für die Änderung des ArmorStands
Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> {
if (!as.isValid()) return; // Sicherheitscheck falls AS gelöscht wurde
if (online) {
restoreName(as);
} else {
String offlineText = "§c● §lOFFLINE §c●";
if (!offlineText.equals(as.getCustomName())) {
as.setCustomName(offlineText);
as.setCustomNameVisible(true);
}
}
});
});
break;
}
}
}
/**
* Stellt den ursprünglichen Namen des ArmorStands wieder her.
* Nutzt den Tag "asname:NAME" als Backup.
*/
private static void restoreName(ArmorStand as) {
for (String t : as.getScoreboardTags()) {
if (t.startsWith("asname:")) {
String originalName = t.substring(7);
String translatedName = ChatColor.translateAlternateColorCodes('&', originalName);
if (!translatedName.equals(as.getCustomName())) {
as.setCustomName(translatedName);
as.setCustomNameVisible(true);
}
return;
}
}
}
}

View File

@@ -111,7 +111,7 @@ public class ServerSwitcherGUI {
private static void connectToServer(Player player, String serverName, Plugin plugin) {
try {
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;
}
@@ -120,13 +120,13 @@ public class ServerSwitcherGUI {
out.writeUTF("Connect");
out.writeUTF(serverName);
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) {
ex.printStackTrace();
player.sendMessage(ChatColor.RED + "Fehler beim Verbinden zum Server.");
NexusLobby.getInstance().getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + ex.getMessage());
player.sendMessage(de.nexuslobby.utils.LangManager.get("server_connect_error"));
} catch (RuntimeException ex) {
// 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());
}
}

View File

@@ -4,6 +4,9 @@ import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
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.entity.Player;
import org.bukkit.event.EventHandler;
@@ -19,7 +22,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
public class ServerSwitcherListener implements Listener {
public class ServerSwitcherListener implements Listener, CommandExecutor {
@EventHandler
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
public void onCompassClick(PlayerInteractEvent e) {
Player p = e.getPlayer();

View File

@@ -6,12 +6,12 @@ import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.GameRule;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType; // DIESER IMPORT HAT GEFEHLT
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
@@ -54,8 +54,31 @@ public class LobbySettingsModule implements Module, Listener {
Set<String> keys = settingsConfig.getConfigurationSection("gamerules").getKeys(false);
for (World world : Bukkit.getWorlds()) {
for (String key : keys) {
String value = String.valueOf(settingsConfig.get("gamerules." + key));
world.setGameRuleValue(key, value);
Object value = settingsConfig.get("gamerules." + key);
updateGameRule(world, key, value);
}
}
}
// Hilfsmethode zur sicheren Anwendung von GameRules in 1.21
@SuppressWarnings("unchecked")
private void updateGameRule(World world, String ruleName, Object value) {
GameRule<?> rule = GameRule.getByName(ruleName);
if (rule == null) return;
if (value instanceof Boolean && rule.getType() == Boolean.class) {
world.setGameRule((GameRule<Boolean>) rule, (Boolean) value);
} else if (value instanceof Integer && rule.getType() == Integer.class) {
world.setGameRule((GameRule<Integer>) rule, (Integer) value);
} else {
// Falls der Wert aus der Config als String kommt, versuchen wir ihn zu parsen
String strValue = String.valueOf(value);
if (rule.getType() == Boolean.class) {
world.setGameRule((GameRule<Boolean>) rule, Boolean.parseBoolean(strValue));
} else if (rule.getType() == Integer.class) {
try {
world.setGameRule((GameRule<Integer>) rule, Integer.parseInt(strValue));
} catch (NumberFormatException ignored) {}
}
}
}
@@ -95,6 +118,7 @@ public class LobbySettingsModule implements Module, Listener {
Material mat = isBool ? ((Boolean)value ? Material.LIME_DYE : Material.GRAY_DYE) : Material.BOOK;
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName("§e" + key);
List<String> lore = new ArrayList<>();
lore.add("§7Aktueller Wert: §f" + value);
@@ -138,13 +162,17 @@ public class LobbySettingsModule implements Module, Listener {
private void handleSubClick(InventoryClickEvent event) {
Player p = (Player) event.getWhoClicked();
if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) return;
if (event.getCurrentItem().getType() == Material.BARRIER) {
ItemStack current = event.getCurrentItem();
if (current == null || current.getType() == Material.AIR) return;
if (current.getType() == Material.BARRIER) {
openMainMenu(p);
return;
}
String key = event.getCurrentItem().getItemMeta().getDisplayName().substring(2);
ItemMeta meta = current.getItemMeta();
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;
Object value = settingsConfig.get(path);
String category = event.getView().getTitle().replace(subTitlePrefix, "");
@@ -154,7 +182,7 @@ public class LobbySettingsModule implements Module, Listener {
settingsConfig.set(path, newValue);
saveSettings();
if (path.startsWith("gamerules.")) {
for (World w : Bukkit.getWorlds()) w.setGameRuleValue(key, String.valueOf(newValue));
for (World w : Bukkit.getWorlds()) updateGameRule(w, key, newValue);
}
} else if (value instanceof Integer) {
int newVal = (Integer) value + (event.getClick().isLeftClick() ? 1 : -1);
@@ -162,7 +190,7 @@ public class LobbySettingsModule implements Module, Listener {
settingsConfig.set(path, newVal);
saveSettings();
if (path.startsWith("gamerules.")) {
for (World w : Bukkit.getWorlds()) w.setGameRuleValue(key, String.valueOf(newVal));
for (World w : Bukkit.getWorlds()) updateGameRule(w, key, newVal);
}
}
@@ -171,7 +199,6 @@ public class LobbySettingsModule implements Module, Listener {
}
private void refreshCategory(Player p, String category) {
// Die refresh-Logik wurde vereinfacht, um Fehler zu vermeiden
if (category.equals("Lobby-Schutz")) openCategory(p, "Lobby-Schutz", "allowPvp", "allowBlockBreaking", "allowBlockPlacing", "allowBlockInteracting", "allowItemDropping", "allowItemPickup", "allowExplosions");
else if (category.equals("Chat")) openCategory(p, "Chat", "announceAdvancements", "commandBlockOutput", "logAdminCommands", "sendCommandFeedback", "showDeathMessages", "reducedDebugInfo");
else if (category.equals("Drops")) openCategory(p, "Drops", "keepInventory", "doEntityDrops", "doMobLoot", "doTileDrops", "mobExplosionDropDecay", "blockExplosionDropDecay", "tntExplosionDropDecay");
@@ -182,12 +209,17 @@ public class LobbySettingsModule implements Module, Listener {
}
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) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name);
if (!lore.isEmpty()) {
List<String> lores = new ArrayList<>();

View File

@@ -84,7 +84,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List
}
}
} 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;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -23,7 +24,13 @@ public class ConfigUpdater {
FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile);
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;
while ((line = reader.readLine()) != null) {
String trimmed = line.trim();
@@ -52,7 +59,7 @@ public class ConfigUpdater {
}
}
} catch (IOException e) {
e.printStackTrace();
Bukkit.getLogger().severe("Fehler beim Config-Update: " + e.getMessage());
}
// 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

@@ -23,14 +23,23 @@ public class PlayerHider implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return;
Material material = Material.valueOf(NexusLobby.getInstance().getConfig().getString("hider.item", "REDSTONE"));
int slot = NexusLobby.getInstance().getConfig().getInt("hider.slot", 8);
var config = NexusLobby.getInstance().getConfig();
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);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§"));
item.setItemMeta(meta);
if (meta != null) {
String allMsg = colorize(config.getString("hider.messages.all", "&aAlle Spieler"));
meta.setDisplayName(allMsg);
item.setItemMeta(meta);
}
event.getPlayer().getInventory().setItem(slot, item);
}
@@ -39,32 +48,40 @@ public class PlayerHider implements Listener {
public void onInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) 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();
String allMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§");
String noneMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.none").replace("&", "§");
var config = NexusLobby.getInstance().getConfig();
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)) {
// Verstecken
hidden.add(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target);
updateItem(player, noneMsg);
player.sendMessage(noneMsg);
} else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) {
// Zeigen
hidden.remove(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target);
updateItem(player, allMsg);
player.sendMessage(allMsg);
}
}
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();
if (meta == null) return;
meta.setDisplayName(name);
item.setItemMeta(meta);
}
private String colorize(String s) {
return s == null ? "" : s.replace("&", "§");
}
}

View File

@@ -1,18 +1,24 @@
package de.nexuslobby.utils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
public class UpdateChecker {
private final NexusLobby plugin;
// URL zur Gitea API für das neueste Release
private final String url = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest";
private static final String API_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) {
this.plugin = plugin;
@@ -20,23 +26,60 @@ public class UpdateChecker {
public void getVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream inputStream = new URL(url).openStream(); Scanner scanner = new Scanner(inputStream)) {
StringBuilder response = new StringBuilder();
while (scanner.hasNextLine()) {
response.append(scanner.nextLine());
HttpURLConnection connection = null;
try {
URL url = new URL(API_URL);
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();
// Einfaches Parsing des JSON "tag_name" Feldes
if (content.contains("\"tag_name\":")) {
String version = content.split("\"tag_name\":\"")[1].split("\"")[0];
// Entferne ein eventuelles 'v' Präfix (z.B. v1.0.0 -> 1.0.0)
version = version.replace("v", "");
consumer.accept(version);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
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)) {
Location spawn = player.getWorld().getSpawnLocation();
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()) {
player.sendMessage(msg.replace("&", "§"));
player.sendMessage(msg);
}
}
}

View File

@@ -27,6 +27,16 @@ lobby:
default-gamemode: Adventure
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
servers:
survival:
ip: "127.0.0.1"
port: 25566
skyblock:
ip: "127.0.0.1"
port: 25567
# --- Tablist Einstellungen ---
tablist:
enabled: true
@@ -54,7 +64,7 @@ items:
portals:
default-particle: "PORTAL"
portal-cooldown: 40 # Ticks, 2 Sekunden
save-file: "portals.yml" # Datei im Plugin-Ordner
save-file: "portals.yml"
# -----------------------------------------------------
# COMPASS MENU
@@ -87,6 +97,13 @@ compass:
lore:
- "&7Zeige was du kannst!"
# -----------------------------------------------------
# PLAYER INSPECT (Statistiken per Rechtsklick)
# -----------------------------------------------------
player_inspect:
enabled: true
gui_title: "&8Statistiken von &6{PLAYER}"
# --- Suppressor / Global Chat Einstellungen ---
suppressor:
enabled: true
@@ -143,3 +160,19 @@ hider:
all: "&aAlle Spieler: &7Sichtbar"
# Anzeigename des Items und Nachricht, wenn alle versteckt sind
none: "&cKeine Spieler: &7Versteckt"
# -----------------------------------------------------
# BALL / SOCCER EINSTELLUNGEN
# -----------------------------------------------------
ball:
enabled: true
# Der Spawnpunkt wird automatisch über /nexus ball setspawn hier gespeichert
spawn:
world: "world"
x: 10.5
y: 65.0
z: 10.5
yaw: 0.0
pitch: 0.0
# Zeit in Sekunden, bis der Ball bei Inaktivität respawnt
respawn_delay: 60

View File

@@ -0,0 +1,164 @@
# =============================================================
# NexusLobby - Lebendige Dialoge v2
# =============================================================
conversations:
parkour_begruessung:
morgens:
dialogue:
- '&6&lTrainer &8» &fGuten Morgen! Zeit für Frühsport! *streckt sich*'
- '&6&lTrainer &8» &fWillkommen bei &e&lNexusLobby&f!'
- '&6&lTrainer &8» &eKlick mich an&f, um den Parkour zu starten!'
mittags:
dialogue:
- '&6&lTrainer &8» &fDie Sonne brennt, der Parkour wartet!'
- '&6&lTrainer &8» &fHast du das Zeug zum &aParkour-Meister&f?'
- '&6&lTrainer &8» &eKlick mich an&f, wenn du dich traust!'
abends:
dialogue:
- '&6&lTrainer &8» &fNoch eine Runde vor dem Schlafengehen?'
- '&6&lTrainer &8» &fZeig bei &e&lNexusLobby&f, was du noch drauf hast!'
- '&6&lTrainer &8» &eKlick mich an&f, für deinen neuen Rekord!'
owner_besuch:
morgens:
dialogue:
- '&dTimmy: &fMami, ist der Owner schon wach? &e*hüpft*'
- '&5Sarah: &7Bestimmt! Er schließt den Server auf.'
- '&dTimmy: &fIch zeig ihm meinen Teddy mit Krone! &a*stolz*'
- '&5Sarah: &7Leise sein, falls er noch arbeitet. *psst*'
mittags:
dialogue:
- '&dTimmy: &fDa ist das Büro! Ich seh die Fenster! &e*rennt*'
- '&5Sarah: &cStopp! &7Nicht so stürmisch, Timmy.'
- '&dTimmy: &fAber Teddy will den Schreibtisch sehen!'
- '&5Sarah: &7Komm an meine Hand. Wir klopfen ganz brav.'
abends:
dialogue:
- '&dTimmy: &fGuck! Im Büro brennt noch Licht! &b*zeigt*'
- '&5Sarah: &7Er arbeitet sicher noch an Updates.'
- '&dTimmy: &fIst er ein Roboter? Er schläft nie! &7*staunt*'
- '&5Sarah: &7Nein, er braucht nur viel Zaubertrank. *lacht*'
fussball_match:
morgens:
dialogue:
- '&eLeon: &fFinn! Aufwachen! Kick den Ball! &e*kickt*'
- '&6Finn: &8*gähnt* &7Mein Fuß schläft noch, Leon...'
- '&eLeon: &fKeine Ausreden! Pass an! &6*kickt hart*'
- '&6Finn: &fHuch! &7*stoppt unsicher* &fBin ja schon wach!'
mittags:
dialogue:
- '&eLeon: &fPass auf! Jetzt kommt mein Spezialschuss!'
- '&6Finn: &fDen hab ich! &e*macht Hechtsprung* &fJaaaa!'
- '&eLeon: &7Wie hast du den denn erwischt? &c*schmollt*'
- '&6Finn: &fIch kenne deine Tricks, Leon! *grinst*'
abends:
dialogue:
- '&eLeon: &7Ich seh den Ball kaum noch. &8*kneift Augen*'
- '&6Finn: &fEgal! Nächstes Tor gewinnt die Krone!'
- '&eLeon: &fHe! Das war Foul! Du hast geschubst! &c*meckert*'
- '&6Finn: &7Körpereinsatz! Fang mich doch! &a*rennt weg*'
baum_drama:
morgens:
dialogue:
- '&aLotte: &fDie Aussicht ist göttlich! &d*atmet tief*'
- '&bSchmidt: &7Lotte, komm runter! Es ist 7 Uhr morgens!'
- '&aLotte: &fIch beobachte Vögel. Ich bin jetzt einer!'
- '&bSchmidt: &7Du brauchst Kaffee, keinen Baum. &8*seufzt*'
mittags:
dialogue:
- '&aLotte: &fIch kann fliegen! Seht mich an! &7*balanciert*'
- '&bSchmidt: &cLotte, komm sofort vom Baum runter!'
- '&aLotte: &fIch bin ein stolzer Adler! &e*breitet Arme aus*'
- '&bSchmidt: &7Du bist eine Frau auf einer Birke! Abstieg!'
abends:
dialogue:
- '&aLotte: &fVon hier sieht man das Feuerwerk besser!'
- '&bSchmidt: &7Es wird kalt. Und da oben sind Spinnen!'
- '&aLotte: &fSpinnen? &7*schaut hektisch* &fWo?!'
- '&bSchmidt: &7Komm zur Leiter. Ich rette dich. &e*grinst*'
familien_spaziergang:
morgens:
dialogue:
- '&9Tom: &7Schaut mal diesen Sonnenaufgang an. &6*genießt*'
- '&5Eva: &7Lass dir Zeit. Die Welt wacht gerade erst auf.'
- '&dJasmin: &fPapa, die Blumen gehen auf! &e*bleibt stehen*'
- '&9Tom: &7Wollen wir zum Bäcker? Frische Brötchen!'
mittags:
dialogue:
- '&9Tom: &7Klar, Jasmin. Wünsch dir was ganz Besonderes!'
- '&5Eva: &7Nicht reinfallen! Das Wasser ist tief. &6*lacht*'
- '&dJasmin: &fDarf ich eine Münze werfen? Bitte! &d*hüpft*'
- '&9Tom: &7Ich wünsche mir... &7*kneift Augen zu* &f...Eis!'
abends:
dialogue:
- '&9Tom: &7Der ganze Spawn leuchtet jetzt schön. &b*guckt*'
- '&5Eva: &7Es wird so herrlich ruhig hier, oder? &d*lächelt*'
- '&dJasmin: &fPapa, darf ich auf deine Schultern? &7*gähnt*'
- '&9Tom: &7Hopp! &e*hebt sie hoch* &7Na, wie ist die Sicht?'
frauen_plausch:
morgens:
dialogue:
- '&9Marie: &7Guten Morgen! Du siehst verschlafen aus.'
- '&fPia: &7Die Party gestern war zu lang. Kaffee... &8*gähnt*'
- '&9Marie: &7Der Bäcker hat frische Zimtschnecken! Komm!'
- '&fPia: &fSorg dafür, dass ich nicht umkippe. &7*lacht*'
mittags:
dialogue:
- '&9Marie: &7Hast du den neuen Shop gesehen? Tolle Erze!'
- '&fPia: &fEcht jetzt? &7Ich dachte, die Mine ist zu.'
- '&9Marie: &7Die haben einen neuen Tunnel! Riesige Smaragde!'
- '&fPia: &fWahnsinn. Ich muss mein Inventar leeren!'
abends:
dialogue:
- '&9Marie: &7Hast du die Lichter am Hafen gesehen? &b*staunt*'
- '&fPia: &7Leider verpasst... ich musste Truhen sortieren.'
- '&9Marie: &7Du arbeitest zu viel, Pia. Genieß den Abend!'
- '&fPia: &7Stimmt. Morgen machen wir blau, okay?'
wettrennen_jungs:
morgens:
dialogue:
- '&bNico: &fErster am Portal! Lauf schneller, Lukas!'
- '&fLukas: &c*keucht* &fWarte! Meine Schuhe sind offen!'
- '&bNico: &fKeine Ausreden! Die Sonne lacht! &a*rennt*'
- '&fLukas: &fNa warte, ich krieg dich noch! &7*sprintet*'
mittags:
dialogue:
- '&bNico: &fKomm schon, du lahme Ente! &a*rast davon*'
- '&fLukas: &c*außer Atem* &7Ich... hab heute schon gefarmt!'
- '&bNico: &fErster! Gewonnen! &7*tanzt* &fHer mit dem Apfel!'
- '&fLukas: &fMorgen gewinnen meine Speed-Stiefel! &c*schwitzt*'
abends:
dialogue:
- '&bNico: &fLetztes Rennen! Verlierer poliert Rüstungen!'
- '&fLukas: &fDiesmal nicht, Nico! &e*nimmt Anlauf*'
- '&bNico: &fOh, jetzt wird es ernst? &7Fertig... LOS!'
- '&fLukas: &fJaaa! Ich bin vorne! Wer ist jetzt lahm?! &a*lacht*'
mutter_kind_spiel:
morgens:
dialogue:
- '&dMia: &fMami, schau! Meine Puppen warten schon. &d*zeigt*'
- '&5Sandra: &7Warten sie auf den Empfang, Mia?'
- '&dMia: &fJa, sie wollen zum Owner! &e*rückt Puppe recht*'
- '&5Sandra: &7Dann müssen sie brav Schlange stehen. &7*lächelt*'
mittags:
dialogue:
- '&dMia: &fGuck! Ein riesiges Hotel aus Klötzen! &e*stapelt*'
- '&5Sandra: &7Oha, wird das ein Dino-Hotel?'
- '&dMia: &fJa! Der Dino frisst nur Kekse! &e*füttert ihn*'
- '&5Sandra: &7Bau fleißig weiter, kleine Architektin.'
abends:
dialogue:
- '&dMia: &fMami, Dino ist müde. &7*gähnt laut*'
- '&5Sandra: &7Pack ihn gut in seine Decke ein, Mia.'
- '&dMia: &fMuss ich meine Klötze einpacken? &c*traurig*'
- '&5Sandra: &7Ja, aber morgen bauen wir ein Schloss!'
links:

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

@@ -0,0 +1,192 @@
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,9 +1,9 @@
name: NexusLobby
main: de.nexuslobby.NexusLobby
version: "1.0.4"
version: "1.1.1"
api-version: "1.21"
author: M_Viper
description: Modular Lobby Plugin
description: Modular Lobby Plugin with an invisible Particle-Parkour system.
softdepend: [LuckPerms, PlaceholderAPI, Vault, WorldGuard]
commands:
@@ -38,15 +38,18 @@ commands:
permission: nexuslobby.build
permission-message: "§cDu hast keine Rechte!"
nexuslobby:
description: Zeigt Informationen über das Plugin an oder lädt es neu
usage: /nexuslobby [reload|setspawn]
aliases: [nexus]
description: Hauptbefehl für Plugin-Verwaltung, Spawn-Setup und Parkour-Konfiguration
usage: /nexuslobby [reload|setspawn|silentjoin|sb|parkour]
aliases: [nexus, lobby]
nexustools:
description: Nexus ArmorStand Editor
description: Nexus ArmorStand Editor (LookAt, Dynamic, etc.)
aliases: [nt, ntools, astools]
nexuscmd:
description: Nexus Command Binder
aliases: [ncmd, ascmd]
description: Nexus Command Binder (Slots 0-9) und Gesprächs-System
usage: /nexuscmd <args>
permission: nexuslobby.armorstand.cmd
permission-message: "§cDu hast keine Rechte!"
aliases: [ncmd, ascmd, conv]
holo:
description: Verwalte Lobby Hologramme (Text-Displays)
usage: /holo <create|delete> <id> [text]
@@ -66,6 +69,21 @@ commands:
spawn:
description: Teleportiert dich zum Lobby-Spawnpunkt
usage: /spawn
aliases: [l, hub]
# --- NEUE PARKOUR DIREKT-BEFEHLE ---
setstart:
description: Markiert einen ArmorStand als Parkour-NPC oder setzt die Start-Location
usage: /setstart
permission: nexuslobby.admin
setcheckpoint:
description: Setzt einen neuen Checkpoint an deiner aktuellen Position
usage: /setcheckpoint
permission: nexuslobby.admin
setfinish:
description: Setzt den Zielpunkt für den Parkour
usage: /setfinish
permission: nexuslobby.admin
permissions:
nexuslobby.portal:
@@ -81,7 +99,7 @@ permissions:
description: Zugriff auf den Server Switcher
default: true
nexuslobby.admin:
description: Voller Zugriff auf Lobby-Gamerules, Einstellungen, Intro, Border und Reload
description: Voller Zugriff auf Lobby-Gamerules, Einstellungen, Intro, Border, Parkour-Setup und Reload
default: op
nexuslobby.build:
description: Erlaubt das Umgehen des Lobby-Schutzes zum Bauen
@@ -90,14 +108,35 @@ permissions:
description: Erlaubt die Nutzung der NexusTools GUI
default: op
nexuslobby.armorstand.cmd:
description: Erlaubt das Binden von Commands via NexusCmd
description: Erlaubt das Binden von Commands und das Verwalten von Conversations
default: op
nexuslobby.armorstand.dynamic:
description: Erlaubt das Markieren von dynamischen NPCs
default: op
nexuslobby.armorstand.lookat:
description: Erlaubt das Aktivieren des Blickkontakts bei NPCs
default: op
nexuslobby.hologram:
description: Erlaubt das Erstellen von Text-Display Hologrammen
default: op
nexuslobby.mapart:
description: Erlaubt das Erstellen von Map-Art Bildern
default: op
nexuslobby.silentjoin:
description: Versteckt die Join-Nachricht für den Spieler (Silent Join)
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:
description: Erlaubt das Setzen der unsichtbaren Parkour-Punkte (Start, Ziel, Checkpoints)
default: op