56 Commits
Author SHA1 Message Date
M_Viper 3ec1d905dc Upload via GUI (84 Dateien) 2026-10-11 17:15:35 +00:00
M_Viper 3a8fe5cf66 Upload via GUI (84 Dateien) 2026-10-11 17:15:33 +00:00
Git Manager GUI 1a75a7db33 Upload folder via GUI - src 2026-05-31 12:42:53 +02:00
Git Manager GUI be78c34222 Upload via Git Manager GUI 2026-05-31 12:42:51 +02:00
Git Manager GUI 2460691df2 Upload folder via GUI - src 2026-05-14 22:29:46 +02:00
Git Manager GUI 46dc0cbe05 Upload folder via GUI - src 2026-04-19 12:09:40 +02:00
Git Manager GUI 79fed93d20 Upload via Git Manager GUI 2026-04-19 12:09:39 +02:00
Git Manager GUI c3a8f15f6d Upload folder via GUI - src 2026-04-18 08:39:58 +02:00
Git Manager GUI b46af01475 Upload via Git Manager GUI 2026-04-18 08:39:56 +02:00
Git Manager GUI 8fe8f2b1af Upload folder via GUI - src 2026-04-08 09:39:54 +02:00
Git Manager GUI ce6847e58e Upload folder via GUI - src 2026-04-08 02:42:24 +02:00
Git Manager GUI 95b0e2f971 Upload folder via GUI - src 2026-04-04 18:47:34 +02:00
Git Manager GUI 045a5f2b1b Upload via Git Manager GUI 2026-04-04 18:47:32 +02:00
M_Viper 8a2c3b4277 Upload via Git Manager GUI - example-conversations.yml 2026-03-27 15:23:50 +00:00
M_Viper 38bbba4c8b Upload via Git Manager GUI - pom.xml 2026-03-27 15:23:49 +00:00
M_Viper d92d0d5328 Update from Git Manager GUI 2026-03-27 10:05:01 +01:00
M_Viper a27c174daa Upload via Git Manager GUI - pom.xml 2026-03-27 09:05:00 +00:00
M_Viper 0dddc0dfb5 Update from Git Manager GUI 2026-02-28 13:37:23 +01:00
M_Viper 7fe5bd62c7 Upload pom.xml via GUI 2026-02-28 12:37:22 +00:00
M_Viper f7f0a0d532 Upload pom.xml via GUI 2026-02-26 23:45:15 +00:00
M_Viper 35dded973b Update from Git Manager GUI 2026-02-26 11:07:51 +01:00
M_Viper 43dac083d4 Upload pom.xml via GUI 2026-02-26 10:07:50 +00:00
M_Viper 9c1b980388 Update from Git Manager GUI 2026-02-06 07:38:13 +01:00
M_Viper 983ca72aaa Upload pom.xml via GUI 2026-02-06 06:38:11 +00:00
M_Viper eed33a4bd7 README.md aktualisiert 2026-02-05 21:52:03 +00:00
M_Viper 0ede50287f Upload pom.xml via GUI 2026-02-05 21:49:12 +00:00
M_Viper 6b0d6fa460 Update from Git Manager GUI 2026-02-05 22:49:10 +01:00
M_Viper d219278689 Upload pom.xml via GUI 2026-02-05 21:44:23 +00:00
M_Viper 1bea420d24 Update from Git Manager GUI 2026-02-05 22:44:21 +01:00
M_Viper 42cd51aa35 Upload file readme.md via GUI 2026-02-05 22:44:20 +01:00
M_Viper ef27111cad spigot-plugin.txt gelöscht 2026-02-02 16:54:48 +00:00
M_Viper cac8d9287d Upload file spigot-plugin.txt via GUI 2026-02-02 17:54:39 +01:00
M_Viper 4bb1086534 spigot-plugin.txt gelöscht 2026-02-02 01:50:52 +00:00
M_Viper e8db790b74 Upload file spigot-plugin.txt via GUI 2026-02-02 02:50:39 +01:00
M_Viper fe48d0e9f3 Upload pom.xml via GUI 2026-02-02 01:32:26 +00:00
M_Viper 2bf1c72971 Upload pom.xml via GUI 2026-02-02 01:16:35 +00:00
M_Viper 7343f2cbf0 Upload pom.xml via GUI 2026-02-01 21:19:41 +00:00
M_Viper 6a892c45db Update from Git Manager GUI 2026-02-01 22:19:40 +01:00
M_Viper ce43cac14f Update from Git Manager GUI 2026-01-31 21:24:57 +01:00
M_Viper 85aaadbf99 Upload pom.xml via GUI 2026-01-31 20:24:56 +00:00
M_Viper 369e226b3b Upload example-conversations.yml via GUI 2026-01-31 20:24:56 +00:00
M_Viper ba20c2a498 example-conversations.yml hinzugefügt 2026-01-24 23:12:15 +00:00
M_Viper 250bf2fea6 README.md aktualisiert 2026-01-24 16:13:45 +00:00
M_Viper 64a03b539e Upload pom.xml via GUI 2026-01-24 16:03:51 +00:00
M_Viper 1db87e81f0 Update from Git Manager GUI 2026-01-24 17:03:48 +01:00
M_Viper 4c847401a0 Update from Git Manager GUI 2026-01-23 22:08:30 +01:00
M_Viper aac2482511 Upload pom.xml via GUI 2026-01-23 21:08:30 +00:00
M_Viper 8ea8ab50f3 Upload pom.xml via GUI 2026-01-23 17:09:05 +00:00
M_Viper addab7245d Update from Git Manager GUI 2026-01-23 18:09:04 +01:00
M_Viper fa6c79aa53 README.md aktualisiert 2026-01-23 11:40:28 +00:00
M_Viper 27b9563a45 Update from Git Manager GUI 2026-01-23 12:17:49 +01:00
M_Viper 96973d44e5 Upload pom.xml via GUI 2026-01-23 11:17:49 +00:00
M_Viper 6630621ba1 Upload pom.xml via GUI 2026-01-23 09:43:54 +00:00
M_Viper 5f2c05d85e Update from Git Manager GUI 2026-01-23 10:43:53 +01:00
M_Viper 9fd0690ba8 Update from Git Manager GUI 2026-01-22 23:16:25 +01:00
M_Viper 233639dd52 Upload pom.xml via GUI 2026-01-22 22:16:25 +00:00
70 changed files with 9767 additions and 968 deletions
+302 -119
View File
@@ -4,197 +4,380 @@
<img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby"> <img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby">
</p> </p>
Ein umfassendes Lobby-Plugin 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) ![Minecraft](https://img.shields.io/badge/Minecraft-1.21+-green)
![Java](https://img.shields.io/badge/Java-21+-orange) ![Java](https://img.shields.io/badge/Java-21+-orange)
![License](https://img.shields.io/badge/License-Proprietary-red) ![License](https://img.shields.io/badge/License-Proprietary-red)
![Version](https://img.shields.io/badge/Version-1.1.0-blue)
--- ---
## Lizenz und Nutzungsbedingungen ## 🎯 Lizenz und Nutzungsbedingungen
**ALLE RECHTE VORBEHALTEN** **ALLE RECHTE VORBEHALTEN**
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 personlichen Gebrauch gestattet - Die Nutzung ist ausschließlich für den persönlichen Gebrauch gestattet.
- Die Weitergabe, Verbreitung oder Veroffentlichung des Plugins ist **strengstens untersagt** - Die Weitergabe, Verbreitung oder Veröffentlichung des Plugins ist **strengstens untersagt**.
- Jegliche Anderung, Modifikation oder Dekompilierung des Codes ist **verboten** - Jegliche Änderung, Modifikation oder Dekompilierung des Codes ist **verboten**.
- Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden - Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden.
- Eine Weitergabe an Dritte ist ohne ausdruckliche schriftliche Genehmigung nicht gestattet - 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 - **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn
- **Void-Schutz** - Automatischer Teleport bei Fall ins Void - **Portal-System** - BungeeCord-Portale für nahtlose Server-Wechsel mit Partikel-Effekten
- **Doppelsprung** - Konfigurierbarer Double-Jump mit Cooldown
- **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus - **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 ### 🛡️ Sicherheit & Protection
- **Scoreboard** - Anpassbares Sidebar-Scoreboard mit Animationen - **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen via proxycheck.io API
- **Tablist** - Header und Footer mit PlaceholderAPI-Support - **Country-Blocker** - Geo-IP Filter (Whitelist/Blacklist für Länder)
- **BossBar** - Animierte Boss-Bar mit wechselnden Nachrichten - **Maintenance-Modus** - Vollständiger Wartungsmodus mit Whitelist-Funktion
- **ActionBar** - Permanente ActionBar-Nachrichten - **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 ### 📊 Visuelle Elemente
- **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen (proxycheck.io API) - **Scoreboard** - Vollständig animiert mit PlaceholderAPI-Support
- **Country-Blocker** - Erlaubt nur bestimmte Lander (Whitelist/Blacklist) - **Tablist** - Dynamische Header/Footer mit Permissions-Anzeige
- **Wartungsmodus** - Sperrt den Server fur nicht-berechtigte Spieler - **BossBar** - Rotierende Nachrichten mit Farb-Effekten
- **Lobby-Schutz** - Verhindert Griefing und unerwunschte Aktionen - **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 ### 🎮 Item-System
- **Portal-System** - Erstelle Portale fur Server-Wechsel (BungeeCord) - **Compass** - Server-Switcher GUI
- **ArmorStand-Tools** - Bearbeite ArmorStands mit GUI und Command-Binding - **Player-Hider** - Sichtbarkeits-Toggle für andere Spieler
- **Server-Switcher** - GUI-basierter Server-Wechsel - **Gadgets** - Konfigurierbares Item-System mit Custom-Slots
- **Spieler verstecken** - Toggle fur Spieler-Sichtbarkeit - **Player-Inspector** - Detaillierte Spieler-Informationen per Click
- **Chat-Suppressor** - Unterdruckung fur globalen Chat
--- ---
## Installation ## 📋 Befehle
### Voraussetzungen ### Haupt-Commands
- Paper/Spigot Server 1.21 oder hoher
- Java 21 oder hoher
### Optionale Abhangigkeiten
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) - Fur Platzhalter-Support
- [LuckPerms](https://luckperms.net/) - Fur 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
| Befehl | Beschreibung | Berechtigung | | Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------| |--------|--------------|--------------|
| `/nexuslobby` | Hauptbefehl mit Hilfe | `nexuslobby.use` | | `/nexuslobby reload` | Lädt das Plugin neu | `nexuslobby.admin` |
| `/nexuslobby reload` | Konfiguration neu laden | `nexuslobby.reload` | | `/nexuslobby setspawn` | Setzt den Lobby-Spawn | `nexuslobby.admin` |
| `/nexuslobby setspawn` | Spawn-Punkt setzen | `nexuslobby.setspawn` | | `/nexuslobby silentjoin <on\|off>` | Versteckter Join/Quit | `nexuslobby.admin` |
| `/build` | Build-Modus umschalten | `nexuslobby.build` | | `/nexuslobby sb <on\|off\|admin\|spieler>` | Scoreboard-Kontrolle | `nexuslobby.admin` |
| `/maintenance` | Wartungsmodus verwalten | `nexuslobby.maintenance` | | `/spawn` | Teleport zum Spawn | `nexuslobby.spawn` |
| `/portal` | Portal-System verwalten | `nexuslobby.portal` |
| `/armorstand` | ArmorStand-Editor | `nexuslobby.armorstand` | ### NPC & ArmorStand
| `/lobbysettings` | Spieler-Einstellungen | `nexuslobby.settings` |
| 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 ```yaml
prefix: "&8[&6NexusLobby&8] " # Spawn-Einstellungen
spawn: spawn:
teleport_on_join: true
teleport_on_respawn: true
world: "world" world: "world"
x: 0.5 x: 0.5
y: 100.0 y: 64.0
z: 0.5 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 enabled: true
cooldown: 3 type: "SQUARE" # SQUARE oder CIRCLE
velocity_y: 1.0 radius: 50.0
velocity_multiplier: 1.5
``` ```
### settings.yml ### conversations.yml
```yaml
player_visibility: true
double_jump: true
flight: false
gamerules: ```yaml
block_break: false conversations:
block_place: false willkommen:
pvp: false dialogue:
hunger: false - "&eWächter: &7Willkommen auf dem Server!"
fall_damage: false - "&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 ```yaml
scoreboard: # ActionBar-Nachrichten
actionbar:
enabled: true enabled: true
title: "&6&lNEXUS LOBBY" speed: 3
lines: messages:
- "&7" - "&6&lWillkommen &8» &eauf unserem Server!"
- "&fSpieler: &a%online%" - "&b&lViel Spaß &8» &7in der Lobby!"
- "&fRang: &e%luckperms_primary_group_name%"
tablist:
enabled: true
header:
- "&6&lNEXUS NETWORK"
footer:
- "&7Spieler online: &a%online%"
# BossBar
bossbar: bossbar:
enabled: true enabled: true
interval: 40
messages: messages:
- "&6Willkommen auf NexusNetwork!" - text: "&6&lNexusLobby &8» &eVersion 1.1.0"
color: "YELLOW"
``` ```
--- ---
## Berechtigungen ## 🌐 Mehrsprachigkeit & Texte
Alle Nachrichten, Hilfetexte und Fehler werden zentral über die Datei `lang.yml` im Ordner `src/main/resources` verwaltet. Dort kannst du für jede Sprache (z.B. Deutsch und Englisch) die Texte pflegen und beliebig erweitern.
**Beispiel für lang.yml:**
```yaml
welcome:
de: "Willkommen auf dem Server!"
en: "Welcome to the server!"
no_permission:
de: "§cKeine Berechtigung."
en: "§cNo permission."
```
**Sprache umstellen:**
Im Code kann die Sprache mit `LangManager.setLanguage("en")` gewechselt werden. Standard ist Deutsch (`de`).
**Texte ingame nutzen:**
Alle Texte werden im Plugin mit `LangManager.get("key")` abgerufen und sind direkt ingame sichtbar. Änderungen in der lang.yml wirken nach einem Reload sofort.
---
## 🔐 Berechtigungen
### Admin-Berechtigungen
| Berechtigung | Beschreibung | | Berechtigung | Beschreibung |
|--------------|--------------| |--------------|--------------|
| `nexuslobby.*` | Alle Berechtigungen | | `nexuslobby.admin` | Voller Zugriff auf alle System-Einstellungen |
| `nexuslobby.use` | Grundlegende Nutzung | | `nexuslobby.armorstand.cmd` | NPCs konfigurieren und Dialoge verknüpfen |
| `nexuslobby.reload` | Konfiguration neu laden | | `nexuslobby.armorstand.use` | Zugriff auf die ArmorStand-Editor GUI |
| `nexuslobby.setspawn` | Spawn setzen | | `nexuslobby.build` | Berechtigung für den Baumodus |
| `nexuslobby.build` | Build-Modus nutzen | | `nexuslobby.portal` | Portale erstellen und löschen |
| `nexuslobby.maintenance` | Wartungsmodus verwalten | | `nexuslobby.portal.give` | Portal-Werkzeug erhalten |
| `nexuslobby.portal` | Portale verwalten | | `nexuslobby.hologram` | Hologramme verwalten |
| `nexuslobby.armorstand` | ArmorStand-Editor nutzen | | `nexuslobby.mapart` | MapArt erstellen |
| `nexuslobby.maintenance` | Wartungsmodus togglen |
### Spieler-Berechtigungen
| Berechtigung | Beschreibung |
|--------------|--------------|
| `nexuslobby.spawn` | Spawn-Command nutzen |
| `nexuslobby.serverswitcher` | Server-Switcher GUI öffnen |
| `nexuslobby.scoreboard.admin` | Admin-Scoreboard sehen |
### Bypass-Berechtigungen ### Bypass-Berechtigungen
| Berechtigung | Beschreibung | | Berechtigung | Beschreibung |
|--------------|--------------| |--------------|--------------|
| `nexuslobby.bypass.protection` | Lobby-Schutz umgehen | | `nexuslobby.bypass.maintenance` | Server trotz Wartungsmodus betreten |
| `nexuslobby.bypass.maintenance` | Wartungsmodus umgehen | | `nexuslobby.bypass.vpn` | VPN-Check überspringen |
| `nexuslobby.bypass.vpn` | VPN-Blocker umgehen | | `nexuslobby.bypass.country` | Country-Check überspringen |
| `nexuslobby.bypass.country` | Country-Blocker umgehen |
--- ---
## 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 | ### Verwendete Technologien
|-------------|--------------| - **Build-Tool**: Maven 3.9+
| `%nexuslobby_online%` | Spieler online | - **JSON-Library**: Gson 2.10.1 (gegenüber json-simple 1.1.1 aktualisiert)
| `%nexuslobby_max_players%` | Maximale Spieler | - **BungeeCord API**: Für Server-Wechsel und Messaging
| `%nexuslobby_maintenance%` | Wartungsmodus Status | - **PlaceholderAPI**: Für dynamische Platzhalter
| `%nexuslobby_build_mode%` | Build-Modus Status |
| `%nexuslobby_double_jump%` | Double-Jump Status | ### 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 1. **Download** der neuesten `NexusLobby-1.1.0.jar` aus den [Releases](../../releases)
- [Issues](../../../issues) - Bug-Reports und Feature-Requests 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) 2024 - Alle Rechte vorbehalten** ## 🐛 Bekannte Issues & Lösungen
Die unbefugte Vervielfaltigung, 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
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:
+6 -6
View File
@@ -6,7 +6,7 @@
<groupId>de.nexuslobby</groupId> <groupId>de.nexuslobby</groupId>
<artifactId>NexusLobby</artifactId> <artifactId>NexusLobby</artifactId>
<version>1.0.1</version> <version>2.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>NexusLobby</name> <name>NexusLobby</name>
@@ -57,9 +57,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.googlecode.json-simple</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>json-simple</artifactId> <artifactId>gson</artifactId>
<version>1.1.1</version> <version>2.10.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -90,8 +90,8 @@
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
<relocations> <relocations>
<relocation> <relocation>
<pattern>org.json.simple</pattern> <pattern>com.google.gson</pattern>
<shadedPattern>de.nexuslobby.libs.json</shadedPattern> <shadedPattern>de.nexuslobby.libs.gson</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
<filters> <filters>
+400
View File
@@ -0,0 +1,400 @@
## 🌐 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.
**Chat-Prefix anpassen:**
Der Prefix vor Plugin-Nachrichten wird in der Server-`config.yml` über `prefix` festgelegt. Farben können mit `&`-Codes angegeben werden:
```yaml
prefix: "&8[&6Nexus&8] "
```
Ändere den Text und die Farben nach Wunsch und lade das Plugin anschließend neu.
---
# NexusLobby
<p align="center">
<img src="https://m-viper.de/img/NexusLobby.png" width="500" alt="NexusLobby">
</p>
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
**ALLE RECHTE VORBEHALTEN**
Dieses Plugin ist urheberrechtlich geschützt. Es gelten folgende Bedingungen:
# Scoreboard ausblenden (Opt-out)
Spieler können das Scoreboard per Befehl ein-/ausblenden. Standardmäßig wird das Scoreboard beim Join angezeigt. Über die Option
```yaml
scoreboard-default-visible: true
```
in der `config.yml` kann festgelegt werden, ob das Scoreboard für neue Spieler standardmäßig sichtbar ist. Bei `false` ist es für alle Spieler nach dem Join ausgeblendet, bis sie es selbst aktivieren.
- 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 Verstoß gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
---
## ✨ Features
### 🤖 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
- **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
### 🛡️ 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
### 📊 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
### 🎮 Item-System
- **Compass** - Server-Switcher GUI
- **Player-Hider** - Sichtbarkeits-Toggle für andere Spieler
- **Gadgets** - Konfigurierbares Item-System mit Custom-Slots
- **Player-Inspector** - Detaillierte Spieler-Informationen per Click
---
## 📋 Befehle
### Haupt-Commands
| Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------|
| `/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
### config.yml (Auszug)
```yaml
# Spawn-Einstellungen
spawn:
world: "world"
x: 0.5
y: 64.0
z: 0.5
yaw: 0.0
pitch: 0.0
# Lobby-Einstellungen
lobby:
allow-fly: false
pvp-enabled: false
build-enabled: false
default-gamemode: Adventure
clear-inventory-on-join: true
# Server-Mapping (für Status-Check)
servers:
survival:
ip: "127.0.0.1"
port: 25566
skyblock:
ip: "127.0.0.1"
port: 25567
# Ball/Soccer-Einstellungen
ball:
respawn_delay: 60 # Sekunden bis Auto-Respawn
# Worldborder
worldborder:
enabled: true
type: "SQUARE" # SQUARE oder CIRCLE
radius: 50.0
```
### conversations.yml
```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 (Auszug)
```yaml
# ActionBar-Nachrichten
actionbar:
enabled: true
speed: 3
messages:
- "&6&lWillkommen &8» &eauf unserem Server!"
- "&b&lViel Spaß &8» &7in der Lobby!"
# BossBar
bossbar:
enabled: true
interval: 40
messages:
- text: "&6&lNexusLobby &8» &eVersion 1.1.0"
color: "YELLOW"
```
---
## 🔐 Berechtigungen
### Admin-Berechtigungen
| Berechtigung | Beschreibung |
|--------------|--------------|
| `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.maintenance` | Server trotz Wartungsmodus betreten |
| `nexuslobby.bypass.vpn` | VPN-Check überspringen |
| `nexuslobby.bypass.country` | Country-Check überspringen |
---
## 🔧 Technische Details
### Systemanforderungen
- **Minecraft**: Paper/Spigot 1.21+ (Spigot-kompatibel, Paper empfohlen)
- **Java**: Java 21 oder höher
- **Dependencies**: LuckPerms, PlaceholderAPI (optional)
### Verwendete Technologien
- **Build-Tool**: Maven 3.9+
- **JSON-Library**: Gson 2.10.1 (gegenüber json-simple 1.1.1 aktualisiert)
- **BungeeCord API**: Für Server-Wechsel und Messaging
- **PlaceholderAPI**: Für dynamische Platzhalter
### Code-Qualität
- ✅ Moderne Java 21 Features (Switch-Expressions, Pattern Matching)
- ✅ Proper Error-Handling (Logger statt printStackTrace)
- ✅ Memory-Management (Tasks & Listeners werden sauber disposed)
- ✅ Config-Validierung mit Default-Werten
- ✅ Optimiertes JSON-Parsing mit Gson
- ✅ Spigot-kompatible BungeeCord Chat API
### Performance-Optimierungen
- **Async-Processing** für API-Calls (Update-Check, VPN-Check)
- **Caching** für Spieler-Daten und Config-Werte
- **Optimierte Entity-Queries** (nur relevante Welten)
- **Effiziente Scheduler-Nutzung** (Tasks werden gecancelt)
---
## 📦 Installation
1. **Download** der neuesten `NexusLobby-1.1.0.jar` aus den [Releases](../../releases)
2. **Upload** der JAR-Datei in den `/plugins/` Ordner
3. **Server-Neustart** durchführen
4. **Konfiguration** anpassen (`config.yml`, `visuals.yml`, etc.)
5. **Permissions** mit LuckPerms oder einem anderen Permissions-Plugin setzen
6. **(Optional)** PlaceholderAPI installieren für erweiterte Platzhalter
---
## 🐛 Bekannte Issues & Lösungen
### Ball spawnt mehrfach
- **Lösung**: Nutze `/nexuslobby ball remove` gefolgt von `/nexuslobby ball setspawn`
- Das Anti-Duplikat-System sollte dies automatisch verhindern
### NPCs schauen nicht zu Spielern
- **Lösung**: Stelle sicher, dass der NPC mit `/nexustools` das LookAt-Feature aktiviert hat
- Prüfe, ob `ArmorStandLookAtModule` in den Logs geladen wird
### Server-Checker zeigt falsche Status
- **Lösung**: Überprüfe die IP/Port-Einstellungen in `config.yml` unter `servers:`
- Stelle sicher, dass die Server erreichbar sind (Firewall, Ports)
---
## 📚 Support & Dokumentation
- **Wiki**: Ausführliche Dokumentation im [Wiki](../../wiki)
- **Bug-Reports**: Erstelle ein [Issue](../../issues) bei technischen Problemen
- **Feature-Requests**: Vorschläge über [Issues](../../issues) mit Label "enhancement"
---
## 📝 Changelog
### Version 1.1.0 (Februar 2026)
- ✨ Soccer/Fußball-System mit realistischer Physik
- ✨ Parkour-System mit Bestenliste und Checkpoints
- ✨ Player-Inspector Modul
- ✨ Config-Validierung mit Auto-Defaults
- 🔧 JSON-Library auf Gson 2.10.1 aktualisiert
- 🔧 Alle printStackTrace() durch Logger ersetzt
- 🔧 Memory-Management verbessert (Tasks & Listeners)
- 🔧 UpdateChecker mit proper JSON-Parsing
- 🐛 Diverse Bug-Fixes und Performance-Optimierungen
---
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.
+335 -73
View File
@@ -13,13 +13,18 @@ import de.nexuslobby.modules.settings.LobbySettingsModule;
import de.nexuslobby.modules.portal.PortalManager; import de.nexuslobby.modules.portal.PortalManager;
import de.nexuslobby.modules.portal.PortalCommand; import de.nexuslobby.modules.portal.PortalCommand;
import de.nexuslobby.modules.servers.ServerSwitcherListener; import de.nexuslobby.modules.servers.ServerSwitcherListener;
import de.nexuslobby.modules.servers.ServerChecker;
import de.nexuslobby.modules.armorstandtools.*; import de.nexuslobby.modules.armorstandtools.*;
import de.nexuslobby.utils.VoidProtection; import de.nexuslobby.modules.gadgets.GadgetModule;
import de.nexuslobby.utils.DoubleJump; import de.nexuslobby.modules.hologram.HologramModule;
import de.nexuslobby.utils.PlayerHider; import de.nexuslobby.modules.mapart.MapArtModule;
import de.nexuslobby.utils.MaintenanceListener; import de.nexuslobby.modules.intro.IntroModule;
import de.nexuslobby.utils.ConfigUpdater; import de.nexuslobby.modules.border.BorderModule;
import de.nexuslobby.utils.UpdateChecker; import de.nexuslobby.modules.parkour.ParkourManager;
import de.nexuslobby.modules.parkour.ParkourListener;
import de.nexuslobby.modules.player.PlayerInspectModule;
import de.nexuslobby.modules.ball.SoccerModule;
import de.nexuslobby.utils.*;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.ComponentBuilder;
@@ -27,18 +32,26 @@ import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.command.PluginCommand; import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class NexusLobby extends JavaPlugin implements Listener { public class NexusLobby extends JavaPlugin implements Listener {
@@ -47,6 +60,18 @@ public class NexusLobby extends JavaPlugin implements Listener {
private PortalManager portalManager; private PortalManager portalManager;
private TablistModule tablistModule; private TablistModule tablistModule;
private LobbySettingsModule lobbySettingsModule; private LobbySettingsModule lobbySettingsModule;
private ItemsModule itemsModule;
private GadgetModule gadgetModule;
private HologramModule hologramModule;
private DynamicArmorStandModule dynamicArmorStandModule;
private MapArtModule mapArtModule;
private IntroModule introModule;
private BorderModule borderModule;
private ParkourManager parkourManager;
private SoccerModule soccerModule;
private ArmorStandLookAtModule armorStandLookAtModule;
private ConversationManager conversationManager;
private File visualsFile; private File visualsFile;
private FileConfiguration visualsConfig; private FileConfiguration visualsConfig;
@@ -54,25 +79,67 @@ public class NexusLobby extends JavaPlugin implements Listener {
private boolean updateAvailable = false; private boolean updateAvailable = false;
private String latestVersion = ""; private String latestVersion = "";
private final Set<UUID> silentPlayers = new HashSet<>();
public static NexusLobby getInstance() { public static NexusLobby getInstance() {
return instance; return instance;
} }
public Set<UUID> getSilentPlayers() {
return silentPlayers;
}
public ConversationManager getConversationManager() {
return conversationManager;
}
public ParkourManager getParkourManager() {
return parkourManager;
}
public SoccerModule getSoccerModule() {
return soccerModule;
}
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; getLogger().info("");
getLogger().info("[NexusLobby] _ __ __ __ __ ");
getLogger().info("[NexusLobby] / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __");
getLogger().info("[NexusLobby] / |/ / _ \\| |/_/ / / / ___/ / / __ \\ / __ \\/ __ \\/ / / /");
getLogger().info("[NexusLobby] / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ / ");
getLogger().info("[NexusLobby] /_/ |_/\\___/_/|_|\\__,_/____/_____/\\____/_.___/_.___/\\__, / ");
getLogger().info("[NexusLobby] /____/ ");
getLogger().info("[NexusLobby] ");
getLogger().info("[NexusLobby] NexusLobby Plugin aktiviert! Version: " + getDescription().getVersion());
instance = this;
initCustomConfigs(); initCustomConfigs();
validateConfig();
// Lade die Sprachdatei
LangManager.load(this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
moduleManager = new ModuleManager(this); moduleManager = new ModuleManager(this);
// --- Parkour & Conversation Initialisierung ---
this.parkourManager = new ParkourManager(this);
this.conversationManager = new ConversationManager(this);
ArmorStandGUI.init(); ArmorStandGUI.init();
registerModules(); registerModules();
moduleManager.enableAll(); moduleManager.enableAll();
registerListeners(); registerListeners();
ServerChecker.startGlobalChecker();
armorStandLookAtModule = new ArmorStandLookAtModule();
startAutoConversationTimer();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new NexusLobbyExpansion().register(); new NexusLobbyExpansion().register();
getLogger().info("NexusLobby PAPI Expansion registriert."); getLogger().info("NexusLobby PAPI Expansion registriert.");
@@ -80,28 +147,24 @@ public class NexusLobby extends JavaPlugin implements Listener {
registerCommands(); registerCommands();
checkUpdates(); checkUpdates();
getLogger().info("NexusLobby wurde erfolgreich gestartet.");
} }
private void checkUpdates() { private void startAutoConversationTimer() {
new UpdateChecker(this).getVersion(version -> { new BukkitRunnable() {
if (!this.getDescription().getVersion().equalsIgnoreCase(version)) { @Override
this.updateAvailable = true; public void run() {
this.latestVersion = version; if (conversationManager != null) {
getLogger().warning("===================================================="); conversationManager.startAllAutomatedConversations();
getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version);
getLogger().warning("Autor: M_Viper");
getLogger().warning("====================================================");
} else {
getLogger().info("NexusLobby ist aktuell (v" + version + ").");
} }
}); }
}.runTaskTimer(this, 100L, 300L);
} }
public void reloadPlugin() { public void reloadPlugin() {
getLogger().info("Plugin Reload wird gestartet..."); getLogger().info("Plugin Reload wird gestartet...");
Bukkit.getScheduler().cancelTasks(this);
if (moduleManager != null) { if (moduleManager != null) {
moduleManager.disableAll(); moduleManager.disableAll();
} }
@@ -111,33 +174,130 @@ public class NexusLobby extends JavaPlugin implements Listener {
reloadVisualsConfig(); reloadVisualsConfig();
Config.load(); Config.load();
if (conversationManager != null) {
conversationManager.setupFile();
}
if (borderModule != null) {
borderModule.reloadConfig();
}
if (portalManager != null) { if (portalManager != null) {
portalManager.loadPortals(); portalManager.loadPortals();
} }
ArmorStandGUI.init(); ArmorStandGUI.init();
if (moduleManager != null) { if (moduleManager != null) {
moduleManager.enableAll(); moduleManager.enableAll();
} }
ServerChecker.startGlobalChecker();
// FIX: ArmorStandLookAtModule als Feld tracken. Bukkit.getScheduler().cancelTasks()
// am Anfang von reloadPlugin() cancelt den alten Task bereits – hier nur
// neu starten und Referenz aktualisieren, damit kein doppelter Task läuft.
armorStandLookAtModule = new ArmorStandLookAtModule();
startAutoConversationTimer();
getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen."); getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen.");
} }
private void checkUpdates() {
new UpdateChecker(this).getVersion(version -> {
String currentVersion = this.getDescription().getVersion();
if (isRemoteVersionNewer(currentVersion, version)) {
this.updateAvailable = true;
this.latestVersion = version;
getLogger().warning("====================================================");
getLogger().warning("Update gefunden! v" + currentVersion + " -> v" + version);
getLogger().warning("====================================================");
} else {
this.updateAvailable = false;
this.latestVersion = "";
getLogger().info("NexusLobby ist aktuell/neu genug (lokal v" + currentVersion + ", remote v" + version + ").");
}
});
}
private boolean isRemoteVersionNewer(String localVersion, String remoteVersion) {
int[] local = extractVersionNumbers(localVersion);
int[] remote = extractVersionNumbers(remoteVersion);
int max = Math.max(local.length, remote.length);
for (int i = 0; i < max; i++) {
int l = i < local.length ? local[i] : 0;
int r = i < remote.length ? remote[i] : 0;
if (r > l) return true;
if (r < l) return false;
}
return false;
}
private int[] extractVersionNumbers(String version) {
if (version == null || version.isBlank()) return new int[0];
java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("\\\\d+").matcher(version);
java.util.List<Integer> parts = new java.util.ArrayList<>();
while (matcher.find()) {
try {
parts.add(Integer.parseInt(matcher.group()));
} catch (NumberFormatException ignored) {
parts.add(0);
}
}
int[] result = new int[parts.size()];
for (int i = 0; i < parts.size(); i++) {
result[i] = parts.get(i);
}
return result;
}
private void registerModules() { private void registerModules() {
moduleManager.registerModule(new ProtectionModule()); moduleManager.registerModule(new ProtectionModule());
moduleManager.registerModule(new ScoreboardModule()); moduleManager.registerModule(new ScoreboardModule());
moduleManager.registerModule(new ItemsModule());
this.itemsModule = new ItemsModule();
moduleManager.registerModule(this.itemsModule);
this.gadgetModule = new GadgetModule();
moduleManager.registerModule(this.gadgetModule);
this.hologramModule = new HologramModule();
moduleManager.registerModule(this.hologramModule);
this.dynamicArmorStandModule = new DynamicArmorStandModule();
moduleManager.registerModule(this.dynamicArmorStandModule);
moduleManager.registerModule(new ArmorStandStatusModule());
this.mapArtModule = new MapArtModule();
moduleManager.registerModule(this.mapArtModule);
this.introModule = new IntroModule();
moduleManager.registerModule(this.introModule);
this.borderModule = new BorderModule();
moduleManager.registerModule(this.borderModule);
moduleManager.registerModule(new SecurityModule()); moduleManager.registerModule(new SecurityModule());
moduleManager.registerModule(new BossBarModule()); moduleManager.registerModule(new BossBarModule());
moduleManager.registerModule(new ActionBarModule()); moduleManager.registerModule(new ActionBarModule());
lobbySettingsModule = new LobbySettingsModule(); this.lobbySettingsModule = new LobbySettingsModule();
moduleManager.registerModule(lobbySettingsModule); moduleManager.registerModule(lobbySettingsModule);
tablistModule = new TablistModule(); this.tablistModule = new TablistModule();
moduleManager.registerModule(tablistModule); moduleManager.registerModule(tablistModule);
portalManager = new PortalManager(this); // Player Inspect Modul registrieren
moduleManager.registerModule(new PlayerInspectModule());
// Soccer Modul registrieren
this.soccerModule = new SoccerModule();
moduleManager.registerModule(this.soccerModule);
this.portalManager = new PortalManager(this);
moduleManager.registerModule(portalManager); moduleManager.registerModule(portalManager);
} }
@@ -149,21 +309,46 @@ public class NexusLobby extends JavaPlugin implements Listener {
getServer().getPluginManager().registerEvents(new PlayerHider(), this); getServer().getPluginManager().registerEvents(new PlayerHider(), this);
getServer().getPluginManager().registerEvents(new MaintenanceListener(), this); getServer().getPluginManager().registerEvents(new MaintenanceListener(), this);
getServer().getPluginManager().registerEvents(new ASTListener(), 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!");
}
}
}
} }
// Priorität auf LOWEST, damit das Inventar VOR den Modulen geleert wird
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (silentPlayers.contains(player.getUniqueId())) {
event.setJoinMessage(null); event.setJoinMessage(null);
}
teleportToSpawn(player);
// FEHLERBEHEBUNG: Inventar leeren, um doppelte Items zu vermeiden
player.getInventory().clear(); player.getInventory().clear();
player.getInventory().setArmorContents(null); // Auch Rüstung entfernen player.getInventory().setArmorContents(null);
BuildCommand.removePlayerFromBuildMode(player); BuildCommand.removePlayerFromBuildMode(player);
String defaultGmName = getConfig().getString("default-gamemode", "ADVENTURE"); String defaultGmName = getConfig().getString("lobby.default-gamemode", "Adventure");
try { try {
player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase())); player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase()));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -172,80 +357,107 @@ public class NexusLobby extends JavaPlugin implements Listener {
if (player.hasPermission("nexuslobby.admin") && updateAvailable) { if (player.hasPermission("nexuslobby.admin") && updateAvailable) {
player.sendMessage(""); player.sendMessage("");
player.sendMessage("§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!"); player.sendMessage(de.nexuslobby.utils.LangManager.get("update_available"));
player.sendMessage("§8» §7Version: §c" + getDescription().getVersion() + " §8-> §a" + latestVersion); player.sendMessage(de.nexuslobby.utils.LangManager.get("update_version").replace("{old}", getDescription().getVersion()).replace("{new}", latestVersion));
TextComponent link = new TextComponent(de.nexuslobby.utils.LangManager.get("update_download_link"));
TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen."); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.spigotmc.org/resources/132388/"));
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases"));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder("§7Öffnet die Gitea Release-Seite").create())); new ComponentBuilder(de.nexuslobby.utils.LangManager.get("update_download_hover")).create()));
player.spigot().sendMessage(link); player.spigot().sendMessage(link);
player.sendMessage(""); player.sendMessage("");
} }
} }
private void initCustomConfigs() { private void teleportToSpawn(Player player) {
if (!getDataFolder().exists()) { FileConfiguration config = getConfig();
getDataFolder().mkdirs(); if (config.contains("spawn.world")) {
String worldName = config.getString("spawn.world");
World world = Bukkit.getWorld(worldName);
if (world != null) {
Location spawnLoc = new Location(
world,
config.getDouble("spawn.x"),
config.getDouble("spawn.y"),
config.getDouble("spawn.z"),
(float) config.getDouble("spawn.yaw"),
(float) config.getDouble("spawn.pitch")
);
player.teleport(spawnLoc);
}
}
} }
private void initCustomConfigs() {
if (!getDataFolder().exists()) getDataFolder().mkdirs();
File configFile = new File(getDataFolder(), "config.yml"); File configFile = new File(getDataFolder(), "config.yml");
if (!configFile.exists()) { if (!configFile.exists()) saveResource("config.yml", false);
saveResource("config.yml", false);
}
ConfigUpdater.updateConfig("config.yml");
reloadConfig(); reloadConfig();
File settingsFile = new File(getDataFolder(), "settings.yml"); File settingsFile = new File(getDataFolder(), "settings.yml");
if (!settingsFile.exists()) { if (!settingsFile.exists()) saveResource("settings.yml", false);
saveResource("settings.yml", false);
}
visualsFile = new File(getDataFolder(), "visuals.yml"); visualsFile = new File(getDataFolder(), "visuals.yml");
if (!visualsFile.exists()) { if (!visualsFile.exists()) saveResource("visuals.yml", false);
saveResource("visuals.yml", false);
} // lang.yml automatisch erstellen, falls nicht vorhanden
File langFile = new File(getDataFolder(), "lang.yml");
if (!langFile.exists()) saveResource("lang.yml", false);
reloadVisualsConfig(); reloadVisualsConfig();
Config.load(); Config.load();
} }
public void reloadVisualsConfig() { public void reloadVisualsConfig() {
if (visualsFile == null) { if (visualsFile == null) visualsFile = new File(getDataFolder(), "visuals.yml");
visualsFile = new File(getDataFolder(), "visuals.yml");
}
visualsConfig = YamlConfiguration.loadConfiguration(visualsFile); visualsConfig = YamlConfiguration.loadConfiguration(visualsFile);
getLogger().info("visuals.yml erfolgreich vom Speicher geladen.");
} }
public FileConfiguration getVisualsConfig() { public FileConfiguration getVisualsConfig() {
if (visualsConfig == null) { if (visualsConfig == null) reloadVisualsConfig();
reloadVisualsConfig();
}
return visualsConfig; return visualsConfig;
} }
@Override @Override
public void onDisable() { public void onDisable() {
// Cancle alle Scheduler-Tasks
Bukkit.getScheduler().cancelTasks(this);
// Stoppe spezifische Tasks
ServerChecker.stopGlobalChecker();
// Unregister alle Event-Listener
org.bukkit.event.HandlerList.unregisterAll((org.bukkit.plugin.Plugin) this);
// Schließe BungeeCord Channel
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
if (moduleManager != null) moduleManager.disableAll();
getLogger().info("NexusLobby disabled"); // Disable alle Module (inkl. eigenes Cleanup)
if (moduleManager != null) {
moduleManager.disableAll();
}
getLogger().info("NexusLobby deaktiviert.");
} }
private void registerCommands() { private void registerCommands() {
LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager); LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule);
NexusLobbyCommand nexusCommand = new NexusLobbyCommand();
PluginCommand portalCmd = this.getCommand("portal"); if (getCommand("portal") != null) {
if (portalCmd != null) { getCommand("portal").setExecutor(new PortalCommand(portalManager));
portalCmd.setExecutor(new PortalCommand(portalManager)); getCommand("portal").setTabCompleter(tabCompleter);
portalCmd.setTabCompleter(tabCompleter);
} }
PluginCommand maintenanceCmd = this.getCommand("maintenance"); if (getCommand("holo") != null) {
if (maintenanceCmd != null) { getCommand("holo").setExecutor(new HoloCommand(hologramModule));
maintenanceCmd.setExecutor(new MaintenanceCommand()); getCommand("holo").setTabCompleter(tabCompleter);
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)); if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this));
@@ -262,16 +474,48 @@ public class NexusLobby extends JavaPlugin implements Listener {
getCommand("nexuscmd").setTabCompleter(tabCompleter); getCommand("nexuscmd").setTabCompleter(tabCompleter);
} }
PluginCommand nexusCmd = this.getCommand("nexuslobby"); if (getCommand("nexuslobby") != null) {
if (nexusCmd != null) { getCommand("nexuslobby").setExecutor(nexusCommand);
nexusCmd.setExecutor(new NexusLobbyCommand()); getCommand("nexuslobby").setTabCompleter(tabCompleter);
nexusCmd.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);
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 { public class NexusLobbyExpansion extends PlaceholderExpansion {
@Override public @NotNull String getIdentifier() { return "nexuslobby"; } @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 @NotNull String getVersion() { return NexusLobby.this.getDescription().getVersion(); }
@Override public boolean persist() { return true; } @Override public boolean persist() { return true; }
@@ -281,11 +525,29 @@ public class NexusLobby extends JavaPlugin implements Listener {
if (params.equalsIgnoreCase("maintenance_status")) return MaintenanceListener.isMaintenance() ? "§cAktiv" : "§aDeaktiviert"; if (params.equalsIgnoreCase("maintenance_status")) return MaintenanceListener.isMaintenance() ? "§cAktiv" : "§aDeaktiviert";
if (params.equalsIgnoreCase("version")) return NexusLobby.this.getDescription().getVersion(); if (params.equalsIgnoreCase("version")) return NexusLobby.this.getDescription().getVersion();
if (params.equalsIgnoreCase("build_mode")) return BuildCommand.isInBuildMode(player) ? "§aAktiv" : "§cInaktiv"; 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";
}
if (params.equalsIgnoreCase("parkour_top_1")) {
return parkourManager != null ? parkourManager.getTopTen(1) : "N/A";
}
if (params.equalsIgnoreCase("parkour_top_2")) {
return parkourManager != null ? parkourManager.getTopTen(2) : "N/A";
}
return null; return null;
} }
} }
public ModuleManager getModuleManager() { return moduleManager; } public ModuleManager getModuleManager() { return moduleManager; }
public PortalManager getPortalManager() { return portalManager; }
public TablistModule getTablistModule() { return tablistModule; } public TablistModule getTablistModule() { return tablistModule; }
public LobbySettingsModule getLobbySettingsModule() { return lobbySettingsModule; } 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; }
} }
@@ -25,7 +25,6 @@ public class ModuleManager {
*/ */
public void enableAll() { public void enableAll() {
for (Module module : modules) { for (Module module : modules) {
plugin.getLogger().info("[NexusLobby] Enabling module: " + module.getName());
module.onEnable(); module.onEnable();
} }
} }
@@ -12,7 +12,6 @@ import java.util.UUID;
public class BuildCommand implements CommandExecutor { public class BuildCommand implements CommandExecutor {
// Liste der Spieler im Baumodus
private static final ArrayList<UUID> buildModePlayers = new ArrayList<>(); private static final ArrayList<UUID> buildModePlayers = new ArrayList<>();
@Override @Override
@@ -30,8 +29,17 @@ public class BuildCommand implements CommandExecutor {
// BAUMODUS DEAKTIVIEREN // BAUMODUS DEAKTIVIEREN
buildModePlayers.remove(uuid); buildModePlayers.remove(uuid);
// Gamemode zurücksetzen (aus Config) // Inventar leeren
String defaultGmName = NexusLobby.getInstance().getConfig().getString("default-gamemode", "ADVENTURE"); player.getInventory().clear();
player.getInventory().setArmorContents(null);
// Lobby-Items über das Modul wiedergeben
if (NexusLobby.getInstance().getItemsModule() != null) {
NexusLobby.getInstance().getItemsModule().giveLobbyItems(player);
}
// Gamemode zurücksetzen
String defaultGmName = NexusLobby.getInstance().getConfig().getString("lobby.default-gamemode", "Adventure");
try { try {
GameMode gm = GameMode.valueOf(defaultGmName.toUpperCase()); GameMode gm = GameMode.valueOf(defaultGmName.toUpperCase());
player.setGameMode(gm); player.setGameMode(gm);
@@ -43,6 +51,9 @@ public class BuildCommand implements CommandExecutor {
} else { } else {
// BAUMODUS AKTIVIEREN // BAUMODUS AKTIVIEREN
buildModePlayers.add(uuid); buildModePlayers.add(uuid);
player.getInventory().clear();
player.getInventory().setArmorContents(null);
player.setGameMode(GameMode.CREATIVE); player.setGameMode(GameMode.CREATIVE);
player.sendMessage("§8» §6§lBuild §8| §7Baumodus §aaktiviert§7."); player.sendMessage("§8» §6§lBuild §8| §7Baumodus §aaktiviert§7.");
@@ -33,6 +33,12 @@ public class GivePortalToolCommand implements CommandExecutor {
// Erstelle das Item // Erstelle das Item
ItemStack wand = new ItemStack(Material.BLAZE_ROD); ItemStack wand = new ItemStack(Material.BLAZE_ROD);
ItemMeta meta = wand.getItemMeta(); ItemMeta meta = wand.getItemMeta();
if (meta == null) {
p.getInventory().addItem(wand);
p.sendMessage(plugin.getName() + " §aDu hast das Portal-Werkzeug erhalten!");
p.playSound(p.getLocation(), org.bukkit.Sound.ENTITY_ITEM_PICKUP, 1.0f, 1.0f);
return true;
}
// Design des Items // Design des Items
meta.setDisplayName("§cPortal-Werkzeug"); meta.setDisplayName("§cPortal-Werkzeug");
@@ -1,9 +1,12 @@
package de.nexuslobby.commands; package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.portal.PortalManager; import de.nexuslobby.modules.portal.PortalManager;
import de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -13,87 +16,196 @@ import java.util.stream.Collectors;
public class LobbyTabCompleter implements TabCompleter { public class LobbyTabCompleter implements TabCompleter {
private final PortalManager portalManager; private final PortalManager portalManager;
private final HologramModule hologramModule;
public LobbyTabCompleter(PortalManager portalManager) { public LobbyTabCompleter(PortalManager portalManager, HologramModule hologramModule) {
this.portalManager = portalManager; this.portalManager = portalManager;
this.hologramModule = hologramModule;
} }
@Override @Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
List<String> suggestions = new ArrayList<>(); List<String> suggestions = new ArrayList<>();
String cmdName = command.getName().toLowerCase();
// --- NexusLobby Hauptbefehl --- // ── NexusLobby Hauptbefehl (/nexus oder /nexuslobby) ──────────────────
if (command.getName().equalsIgnoreCase("nexuslobby") || command.getName().equalsIgnoreCase("nexus")) { if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) {
// /nexuslobby <?>
if (args.length == 1) { if (args.length == 1) {
if (sender.hasPermission("nexuslobby.admin")) suggestions.add("reload"); if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList(
"reload", "setspawn", "silentjoin", "parkour", "ball", "cleanbubbles", "gadgetshield"
));
}
suggestions.add("sb"); suggestions.add("sb");
} else if (args.length == 2 && args[0].equalsIgnoreCase("sb")) {
suggestions.add("on"); // /nexuslobby <sub> <?>
suggestions.add("off"); } else if (args.length == 2) {
if (sender.hasPermission("nexuslobby.scoreboard.admin")) { switch (args[0].toLowerCase()) {
suggestions.add("admin"); case "sb" -> {
suggestions.add("spieler"); suggestions.addAll(Arrays.asList("on", "off"));
if (sender.hasPermission("nexuslobby.scoreboard.admin"))
suggestions.addAll(Arrays.asList("admin", "spieler"));
}
case "silentjoin" -> suggestions.addAll(Arrays.asList("on", "off"));
case "parkour" -> {
if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList(
"setstart", "setfinish", "setcheckpoint",
"holo", "reset", "clear", "removeall", "info"
));
} else {
// Normale Spieler können nur ihren Run abbrechen
suggestions.add("reset");
}
}
case "ball" -> {
if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList(
"setspawn", "respawn", "remove", "goal", "score"
));
} }
} }
} }
// --- Wartungsmodus --- // /nexuslobby <sub> <sub2> <?>
else if (command.getName().equalsIgnoreCase("maintenance")) { } else if (args.length == 3) {
if (args.length == 1) { switch (args[0].toLowerCase()) {
suggestions.add("on"); // Für setstart / setfinish / setcheckpoint → Strecken-Nummer 1 oder 2
suggestions.add("off"); case "parkour" -> {
String parkSub = args[1].toLowerCase();
if (parkSub.equals("setstart")
|| parkSub.equals("setfinish")
|| parkSub.equals("setcheckpoint")
|| parkSub.equals("holo")) {
suggestions.addAll(Arrays.asList("1", "2"));
}
}
case "ball" -> {
switch (args[1].toLowerCase()) {
case "goal" -> suggestions.addAll(Arrays.asList(
"pos1", "pos2", "save", "delete", "list", "show", "debug"
));
case "score" -> suggestions.add("reset");
}
} }
} }
// --- Portalsystem --- // /nexuslobby <sub> <sub2> <sub3> <?>
else if (command.getName().equalsIgnoreCase("portal")) { } else if (args.length == 4) {
if (args[0].equalsIgnoreCase("ball") && args[1].equalsIgnoreCase("goal")) {
switch (args[2].toLowerCase()) {
case "save" -> suggestions.add("<TorName>");
case "delete" -> {
var section = NexusLobby.getInstance().getConfig()
.getConfigurationSection("ball.goals");
if (section != null) suggestions.addAll(section.getKeys(false));
}
}
}
// /nexuslobby ball goal save <Name> <?> → Team 1 oder 2
} else if (args.length == 5) {
if (args[0].equalsIgnoreCase("ball")
&& args[1].equalsIgnoreCase("goal")
&& args[2].equalsIgnoreCase("save")) {
suggestions.addAll(Arrays.asList("1", "2"));
}
}
}
// ── Hologram Befehl ───────────────────────────────────────────────────
else if (cmdName.equals("holo")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("create"); suggestions.addAll(Arrays.asList("create", "delete"));
suggestions.add("delete");
suggestions.add("list");
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
suggestions.addAll(portalManager.getPortalNames()); if (hologramModule != null) suggestions.addAll(hologramModule.getHologramIds());
} }
} }
// --- NexusTools (ehemals astools) --- // ── Wartungsmodus ─────────────────────────────────────────────────────
else if (command.getName().equalsIgnoreCase("nexustools") || command.getName().equalsIgnoreCase("astools") || command.getName().equalsIgnoreCase("nt")) { else if (cmdName.equals("maintenance")) {
if (args.length == 1) suggestions.addAll(Arrays.asList("on", "off"));
}
// ── Portalsystem ──────────────────────────────────────────────────────
else if (cmdName.equals("portal")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("reload"); suggestions.addAll(Arrays.asList("create", "delete", "list"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (portalManager != null) suggestions.addAll(portalManager.getPortalNames());
} }
} }
// --- NexusCmd (ehemals ascmd) --- // ── MapArt ────────────────────────────────────────────────────────────
else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ascmd") || command.getName().equalsIgnoreCase("ncmd")) { else if (cmdName.equals("mapart")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("name"); // NEU suggestions.add("https://");
suggestions.add("list"); } else if (args.length == 2) {
suggestions.add("add"); suggestions.addAll(Arrays.asList("1x1", "3x2", "6x4", "8x5"));
suggestions.add("remove"); }
} }
// Vorschläge für: /nexuscmd name <Text>
else if (args.length == 2 && args[0].equalsIgnoreCase("name")) { // ── Intro System ──────────────────────────────────────────────────────
suggestions.add("none"); else if (cmdName.equals("intro")) {
suggestions.add("<Text>"); if (args.length == 1) suggestions.addAll(Arrays.asList("add", "clear", "start"));
} }
// Vorschläge für: /nexuscmd add <delay> <cooldown> <type>
else if (args.length == 2 && args[0].equalsIgnoreCase("add")) { // ── WorldBorder ───────────────────────────────────────────────────────
suggestions.add("0"); else if (cmdName.equals("border")) {
} if (args.length == 1) {
else if (args.length == 3 && args[0].equalsIgnoreCase("add")) { suggestions.addAll(Arrays.asList("circle", "square", "disable"));
suggestions.add("0"); } else if (args.length == 2 && args[0].equalsIgnoreCase("circle")) {
} suggestions.addAll(Arrays.asList("10", "25", "50", "100", "250"));
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) { }
suggestions.add("player"); }
suggestions.add("console");
suggestions.add("bungee"); // ── NexusCmd / ArmorStandTools ────────────────────────────────────────
} else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd")
else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) { || cmdName.equals("ascmd") || cmdName.equals("conv")) {
suggestions.add("<Servername>"); if (args.length == 1) {
suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say", "clearbubbles"));
} else if (args.length == 2) {
switch (args[0].toLowerCase()) {
case "add" -> suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9"));
case "name" -> suggestions.addAll(Arrays.asList("<Anzeigename>", "none"));
case "conv" -> suggestions.addAll(Arrays.asList("select1","select2","select3","select4","link","unlink","start"));
case "remove" -> suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9","all"));
case "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")
&& (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")) {
var section = NexusLobby.getInstance().getConfig().getConfigurationSection("servers");
if (section != null) suggestions.addAll(section.getKeys(false));
}
}
// ── 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) {
if (args[0].equalsIgnoreCase("say"))
suggestions.add("<Nachricht>");
else if (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) {
if (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 dem, was der Spieler bereits getippt hat
return suggestions.stream() return suggestions.stream()
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) .filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -2,89 +2,353 @@ package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.ScoreboardModule; import de.nexuslobby.modules.ScoreboardModule;
import de.nexuslobby.modules.hologram.HologramModule;
import de.nexuslobby.modules.parkour.ParkourManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class NexusLobbyCommand implements CommandExecutor { public class NexusLobbyCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// Sub-Befehl: /nexus sb <on|off|admin|spieler> if (!(sender instanceof Player player)) {
if (args.length >= 2 && args[0].equalsIgnoreCase("sb")) { sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player"));
if (!(sender instanceof Player)) {
sender.sendMessage("§cDieser Befehl ist nur für Spieler!");
return true; return true;
} }
Player player = (Player) sender;
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) { String cmdName = command.getName().toLowerCase();
player.sendMessage("§cDas Scoreboard-Modul ist aktuell deaktiviert."); ParkourManager pm = NexusLobby.getInstance().getParkourManager();
// --- DIREKTE KURZ-BEFEHLE ---
if (cmdName.equalsIgnoreCase("setstart")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
// Standard: Strecke 1 setzen
pm.setStartLocation(player, 1);
return true; return true;
} }
if (cmdName.equalsIgnoreCase("setcheckpoint")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setCheckpoint(player, 1);
return true;
}
if (cmdName.equalsIgnoreCase("setfinish")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setFinishLocation(player, 1);
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(de.nexuslobby.utils.LangManager.get("teleport_spawn"));
} else {
player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_world_missing"));
}
} else {
player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_not_set"));
}
return true;
}
// --- HAUPTBEFEHL /NEXUSLOBBY oder /NEXUS ---
if (args.length == 0) {
sendInfo(player);
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;
case "cleanbubbles":
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
handleCleanBubbles(player);
break;
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 "gadgetshield":
if (!player.hasPermission("nexuslobby.admin") && !player.isOp()) return noPerm(player);
de.nexuslobby.modules.gadgets.GadgetShield.toggle(player);
break;
case "ball":
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":
handleParkour(player, args, pm);
break;
default:
sendInfo(player);
break;
}
return true;
}
// ─────────────────────────────────────────────────────────────────────────
// Parkour-Handler
// ─────────────────────────────────────────────────────────────────────────
private void handleParkour(Player player, String[] args, ParkourManager pm) {
if (args.length < 2) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage"));
// Strecken-Info ausgeben
player.sendMessage(pm.getTrackInfo());
return;
}
String sub = args[1].toLowerCase(); String sub = args[1].toLowerCase();
// Nur "reset" kann auch ohne Admin-Recht ausgeführt werden (eigenen Lauf abbrechen)
if (!player.hasPermission("nexuslobby.admin") && !sub.equals("reset")) {
noPerm(player);
return;
}
switch (sub) { switch (sub) {
case "on":
sbModule.setVisibility(player, true); // /nexus parkour setstart <1|2>
case "setstart": {
int track = parseTrack(args, 2, player);
if (track == -1) return;
handleSetStart(player, pm, track);
break; break;
case "off":
sbModule.setVisibility(player, false);
break;
case "admin":
if (!player.hasPermission("nexuslobby.scoreboard.admin")) {
player.sendMessage("§cKeine Rechte für den Admin-Modus.");
return true;
} }
sbModule.setAdminMode(player, true);
// /nexus parkour setfinish <1|2>
case "setfinish": {
int track = parseTrack(args, 2, player);
if (track == -1) return;
pm.setFinishLocation(player, track);
break; break;
case "spieler":
if (!player.hasPermission("nexuslobby.scoreboard.admin")) {
player.sendMessage("§cKeine Rechte für den Admin-Modus.");
return true;
} }
sbModule.setAdminMode(player, false);
// /nexus parkour setcheckpoint <1|2>
case "setcheckpoint": {
int track = parseTrack(args, 2, player);
if (track == -1) return;
pm.setCheckpoint(player, track);
break; 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;
case "info":
player.sendMessage(pm.getTrackInfo());
break;
// /nexus parkour holo <1|2> – Leaderboard-Hologramm an aktueller Position setzen
case "holo": {
int track = parseTrack(args, 2, player);
if (track == -1) return;
HologramModule hm = NexusLobby.getInstance().getHologramModule();
if (hm == null) {
player.sendMessage("§cHologram-Modul nicht verfügbar!");
return;
}
String holoId = "parkour_top_" + track;
List<String> pages = new java.util.ArrayList<>();
pages.add("%nexuslobby_parkour_top_" + track + "%");
hm.createHologram(holoId, player.getLocation(), pages);
player.sendMessage("§8[§6Parkour§8] §aLeaderboard-Hologramm für §eStrecke " + track
+ " §agesetzt! §8(ID: §7" + holoId + "§8)");
player.sendMessage("§8[§6Parkour§8] §7Das Hologramm aktualisiert sich automatisch via PlaceholderAPI.");
break;
}
default: default:
player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>"); player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand"));
break; break;
} }
}
/**
* Liest die Track-Nummer aus args[index].
* Gibt 1 oder 2 zurück; bei Fehler sendet er eine Nachricht und gibt -1 zurück.
*/
private int parseTrack(String[] args, int index, Player player) {
if (args.length <= index) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cBitte gib eine Strecken-Nummer an: §e1 §7oder §e2");
return -1;
}
int track;
try {
track = Integer.parseInt(args[index]);
} catch (NumberFormatException e) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cUngültige Strecken-Nummer. Bitte §e1 §7oder §e2 §7verwenden.");
return -1;
}
if (track < 1 || track > 2) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cNur Strecke §e1 §7oder §e2 §7sind erlaubt.");
return -1;
}
return track;
}
private void handleSetStart(Player player, ParkourManager pm, int track) {
// NPC-Erkennung: schaut der Spieler auf einen 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, track);
}
// ─────────────────────────────────────────────────────────────────────────
// Sonstige Handler
// ─────────────────────────────────────────────────────────────────────────
private void handleCleanBubbles(Player player) {
de.nexuslobby.modules.armorstandtools.ConversationManager cm =
NexusLobby.getInstance().getConversationManager();
if (cm == null) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cConversationManager ist nicht aktiv.");
return;
}
cm.clearHangingBubbles();
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aAlle hängengebliebenen Sprechblasen wurden entfernt.");
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
}
private boolean noPerm(Player player) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
return true; return true;
} }
// Sub-Befehl: /nexus reload private void handleScoreboard(Player player, String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { if (args.length < 2) {
if (!sender.hasPermission("nexuslobby.admin")) { player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_usage"));
sender.sendMessage("§cDu hast keine Berechtigung für diesen Befehl."); return;
return true; }
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_module_disabled"));
return;
}
switch (args[1].toLowerCase()) {
case "on" -> sbModule.setVisibility(player, true);
case "off" -> sbModule.setVisibility(player, false);
case "admin" -> {
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true);
else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
}
case "spieler" -> {
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false);
else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
}
}
} }
sender.sendMessage("§7[§6NexusLobby§7] §eKonfigurationen und Module werden neu geladen..."); private Location getSpawnFromConfig(FileConfiguration config) {
NexusLobby.getInstance().reloadPlugin(); World world = Bukkit.getWorld(config.getString("spawn.world", "world"));
sender.sendMessage("§7[§6NexusLobby§7] §aDas Plugin wurde erfolgreich neu geladen!"); if (world == null) return null;
return true; return new Location(world,
config.getDouble("spawn.x"), config.getDouble("spawn.y"), config.getDouble("spawn.z"),
(float) config.getDouble("spawn.yaw"), (float) config.getDouble("spawn.pitch"));
} }
// Standard-Info private void sendInfo(Player player) {
String version = NexusLobby.getInstance().getDescription().getVersion(); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_header"));
sender.sendMessage("§8§m--------------------------------------"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_title"));
sender.sendMessage("§6§lNexusLobby §7- Informationen"); player.sendMessage("");
sender.sendMessage(""); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_spawn"));
sender.sendMessage("§ePlugin Name: §fNexusLobby"); player.sendMessage("§e/nexus parkour setstart <1|2> §7- Start setzen");
sender.sendMessage("§eVersion: §f" + version); player.sendMessage("§e/nexus parkour setcheckpoint <1|2> §7- Checkpoint setzen");
sender.sendMessage("§eAutor: §fM_Viper"); player.sendMessage("§e/nexus parkour setfinish <1|2> §7- Ziel setzen");
sender.sendMessage(""); player.sendMessage("§e/nexus parkour holo <1|2> §7- Top-10 Hologramm setzen");
sender.sendMessage("§6Befehle:"); player.sendMessage("§e/nexus parkour reset §7- Eigenen Run abbrechen");
sender.sendMessage("§f/nexus reload §7- Plugin neu laden"); player.sendMessage("§e/nexus parkour clear §7- §cTop-10 Liste löschen");
sender.sendMessage("§f/nexus sb <on|off> §7- Scoreboard schalten"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_removeall"));
if (sender.hasPermission("nexuslobby.scoreboard.admin")) { player.sendMessage(de.nexuslobby.utils.LangManager.get("info_ball"));
sender.sendMessage("§f/nexus sb <admin|spieler> §7- Modus wechseln"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_setspawn"));
} player.sendMessage(de.nexuslobby.utils.LangManager.get("info_scoreboard"));
sender.sendMessage("§8§m--------------------------------------"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_reload"));
player.sendMessage("§e/nexuslobby cleanbubbles §7- Hängende Sprechblasen entfernen");
return true; if (player.hasPermission("nexuslobby.admin") || player.isOp())
player.sendMessage("§e/nexuslobby gadgetshield §7- Gadget-Schutz für Admins ein/ausschalten");
player.sendMessage(de.nexuslobby.utils.LangManager.get("info_footer"));
} }
} }
@@ -0,0 +1,47 @@
package de.nexuslobby.modules;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.UUID;
public class BuildModule implements Module {
private final ArrayList<UUID> buildModePlayers = new ArrayList<>();
@Override
public String getName() {
return "Build";
}
@Override
public void onEnable() {
// Falls du hier den Command registrierst, achte darauf,
// dass er nicht doppelt in der NexusLobby.java registriert wird.
}
public void toggleBuildMode(Player player) {
if (!player.hasPermission("nexuslobby.build")) return;
if (buildModePlayers.contains(player.getUniqueId())) {
buildModePlayers.remove(player.getUniqueId());
player.setGameMode(GameMode.SURVIVAL);
player.getInventory().clear();
// KORREKTUR: Hier muss getItemsModule() mit "s" stehen!
if (NexusLobby.getInstance().getItemsModule() != null) {
NexusLobby.getInstance().getItemsModule().giveLobbyItems(player);
}
} else {
buildModePlayers.add(player.getUniqueId());
player.setGameMode(GameMode.CREATIVE);
player.getInventory().clear();
}
}
@Override
public void onDisable() {}
}
@@ -1,12 +1,18 @@
package de.nexuslobby.modules; package de.nexuslobby.modules;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module; import de.nexuslobby.api.Module;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class ItemsModule implements Module, Listener { public class ItemsModule implements Module, Listener {
@@ -17,12 +23,98 @@ public class ItemsModule implements Module, Listener {
@Override @Override
public void onEnable() { public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, Bukkit.getPluginManager().getPlugin("NexusLobby")); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
} }
@EventHandler @EventHandler
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
event.getPlayer().getInventory().addItem(new ItemStack(Material.COMPASS)); Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
giveLobbyItems(event.getPlayer());
}, 2L);
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
ItemStack item = event.getItem();
if (item == null || item.getType() == Material.AIR) return;
if (!item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return;
if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
FileConfiguration config = NexusLobby.getInstance().getConfig();
String displayName = item.getItemMeta().getDisplayName();
// 1. Baumodus Logik
String buildName = colorize(config.getString("items.lobby-tools.build-toggle.displayname", "&aBaumodus"));
if (displayName.equals(buildName)) {
player.performCommand("build");
event.setCancelled(true);
return;
}
// 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 (falls vorhanden)
var gadgetModule = NexusLobby.getInstance().getGadgetModule();
if (gadgetModule != null) {
gadgetModule.openGUI(player);
}
event.setCancelled(true);
return;
}
// 3. Kompass / Teleporter Logik (optional, falls du den Befehl schon hast)
String compassName = colorize(config.getString("items.lobby-tools.compass.displayname", "&eTeleporter"));
if (displayName.equals(compassName)) {
// Hier könnte z.B. player.performCommand("cp"); stehen
}
}
}
public void giveLobbyItems(Player player) {
player.getInventory().clear();
FileConfiguration config = NexusLobby.getInstance().getConfig();
// 1. Kompass (Slot 4)
if (config.getBoolean("items.lobby-tools.compass.enabled", true)) {
int slot = config.getInt("items.lobby-tools.compass.slot", 4);
String name = config.getString("items.lobby-tools.compass.displayname", "&eTeleporter");
player.getInventory().setItem(slot, createItem(Material.COMPASS, name));
}
// 2. Baumodus (Slot 0)
if (player.isOp() || player.hasPermission("nexuslobby.build")) {
if (config.getBoolean("items.lobby-tools.build-toggle.enabled", true)) {
int slot = config.getInt("items.lobby-tools.build-toggle.slot", 0);
String name = config.getString("items.lobby-tools.build-toggle.displayname", "&aBaumodus");
player.getInventory().setItem(slot, createItem(Material.REDSTONE_TORCH, name));
}
}
// 3. Gadgets (Slot 8)
if (config.getBoolean("items.lobby-tools.gadget.enabled", true)) { // Auf true gesetzt, damit es erscheint
int slot = config.getInt("items.lobby-tools.gadget.slot", 8);
String name = config.getString("items.lobby-tools.gadget.displayname", "&bGadgets");
player.getInventory().setItem(slot, createItem(Material.CHEST, name));
}
player.updateInventory();
}
private ItemStack createItem(Material mat, String name) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(colorize(name));
item.setItemMeta(meta);
}
return item;
}
private String colorize(String s) {
return (s == null) ? "" : s.replace("&", "§");
} }
@Override @Override
@@ -6,11 +6,14 @@ import de.nexuslobby.commands.BuildCommand;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
@@ -30,7 +33,11 @@ public class ProtectionModule implements Module, Listener {
} }
@Override @Override
public void onDisable() {} public void onDisable() {
// FIX: Listener nach dem Deaktivieren abmelden, damit nach /reload
// keine doppelten Events ausgelöst werden.
HandlerList.unregisterAll(this);
}
@EventHandler @EventHandler
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
@@ -91,4 +98,22 @@ public class ProtectionModule implements Module, Listener {
} }
} }
} }
// FIX: Fehlender Explosionsschutz - settings.yml hat allowExplosions: false,
// aber bisher gab es keinen Handler dafür.
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (!getSettings().getBoolean("allowExplosions", false)) {
event.blockList().clear();
event.setCancelled(true);
}
}
@EventHandler
public void onBlockExplode(BlockExplodeEvent event) {
if (!getSettings().getBoolean("allowExplosions", false)) {
event.blockList().clear();
event.setCancelled(true);
}
}
} }
@@ -2,6 +2,7 @@ package de.nexuslobby.modules;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module; import de.nexuslobby.api.Module;
import io.papermc.paper.scoreboard.numbers.NumberFormat;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@@ -9,20 +10,30 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.*; import org.bukkit.scoreboard.*;
import org.bukkit.scoreboard.Criteria;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class ScoreboardModule implements Module { import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class ScoreboardModule implements Module, Listener {
private final NexusLobby plugin = NexusLobby.getInstance(); private final NexusLobby plugin = NexusLobby.getInstance();
private boolean placeholderAPIEnabled;
// Speicher für die aktuellen Spieler-Einstellungen (bis zum Restart/Reload)
private final Set<UUID> hiddenPlayers = new HashSet<>(); private final Set<UUID> hiddenPlayers = new HashSet<>();
private final Set<UUID> adminModePlayers = new HashSet<>(); private final Set<UUID> adminModePlayers = new HashSet<>();
// WICHTIG: Hier speichern wir die Scoreboards pro Spieler, um sie wiederzuverwenden
private final Map<UUID, Scoreboard> playerBoards = new HashMap<>();
@Override @Override
public String getName() { public String getName() {
return "Scoreboard"; return "Scoreboard";
@@ -33,6 +44,10 @@ public class ScoreboardModule implements Module {
FileConfiguration vConfig = plugin.getVisualsConfig(); FileConfiguration vConfig = plugin.getVisualsConfig();
if (!vConfig.getBoolean("scoreboard.enabled", true)) return; if (!vConfig.getBoolean("scoreboard.enabled", true)) return;
Bukkit.getPluginManager().registerEvents(this, plugin);
placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null;
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
@@ -43,8 +58,26 @@ public class ScoreboardModule implements Module {
}.runTaskTimer(plugin, 0L, vConfig.getLong("scoreboard.update_ticks", 20L)); }.runTaskTimer(plugin, 0L, vConfig.getLong("scoreboard.update_ticks", 20L));
} }
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
boolean defaultVisible = plugin.getConfig().getBoolean("scoreboard-default-visible", true);
if (!defaultVisible) {
hiddenPlayers.add(player.getUniqueId());
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
// Sicherstellen, dass kein altes Board gespeichert ist
playerBoards.remove(player.getUniqueId());
} else {
hiddenPlayers.remove(player.getUniqueId());
// Beim Einloggen initialisieren, damit es nicht beim ersten Tick flackert
if (!playerBoards.containsKey(player.getUniqueId())) {
playerBoards.put(player.getUniqueId(), Bukkit.getScoreboardManager().getNewScoreboard());
player.setScoreboard(playerBoards.get(player.getUniqueId()));
}
}
}
private void updateSidebar(Player player) { private void updateSidebar(Player player) {
// 1. Prüfen, ob der Spieler das Scoreboard ausgeblendet hat
if (hiddenPlayers.contains(player.getUniqueId())) { if (hiddenPlayers.contains(player.getUniqueId())) {
if (player.getScoreboard() != Bukkit.getScoreboardManager().getMainScoreboard()) { if (player.getScoreboard() != Bukkit.getScoreboardManager().getMainScoreboard()) {
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
@@ -53,49 +86,81 @@ public class ScoreboardModule implements Module {
} }
FileConfiguration vConfig = plugin.getVisualsConfig(); FileConfiguration vConfig = plugin.getVisualsConfig();
Scoreboard board = player.getScoreboard();
if (board == Bukkit.getScoreboardManager().getMainScoreboard()) { String configPath = (adminModePlayers.contains(player.getUniqueId())
&& player.hasPermission("nexuslobby.scoreboard.admin"))
? "scoreboard.owner"
: "scoreboard.default";
String title = vConfig.getString(configPath + ".title", "&6&lNexusLobby");
List<String> lines = vConfig.getStringList(configPath + ".lines");
// ---------------------------------------------------------
// FIX: Statt getNewScoreboard() holen wir das gespeicherte Board
// oder erstellen es genau einmal beim ersten Mal.
// ---------------------------------------------------------
Scoreboard board = playerBoards.get(player.getUniqueId());
if (board == null) {
board = Bukkit.getScoreboardManager().getNewScoreboard(); board = Bukkit.getScoreboardManager().getNewScoreboard();
playerBoards.put(player.getUniqueId(), board);
player.setScoreboard(board); player.setScoreboard(board);
} }
Objective obj = board.getObjective("lobby"); // Altes Objective entfernen
if (obj == null) { Objective oldObj = board.getObjective("lobby");
obj = board.registerNewObjective("lobby", "dummy", "title"); if (oldObj != null) oldObj.unregister();
// Alle alten Zeilen-Teams sicher entfernen (großzügige Obergrenze)
for (int i = 0; i < Math.max(lines.size() + 5, 32); i++) {
Team old = board.getTeam("line_" + i);
if (old != null) old.unregister();
}
// Paper 1.21: Objective mit Component-Titel registrieren
String translatedTitle = translate(player, title);
Objective obj = board.registerNewObjective(
"lobby",
Criteria.DUMMY,
net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
.legacySection()
.deserialize(translatedTitle)
);
obj.setDisplaySlot(DisplaySlot.SIDEBAR); obj.setDisplaySlot(DisplaySlot.SIDEBAR);
}
// 2. Pfad-Logik basierend auf dem Modus (/nexus sb admin/spieler) // Zahlen global für dieses Objective ausblenden (Paper 1.20.4+ API)
String configPath = "scoreboard.default"; obj.numberFormat(NumberFormat.blank());
// Wenn im Admin-Modus UND Rechte vorhanden -> owner Sektion
if (adminModePlayers.contains(player.getUniqueId()) && player.hasPermission("nexuslobby.scoreboard.admin")) {
configPath = "scoreboard.owner";
} else {
// Standardmäßig default nutzen
configPath = "scoreboard.default";
}
String title = vConfig.getString(configPath + ".title", "&6&lNexusLobby");
obj.setDisplayName(translate(player, title));
List<String> lines = vConfig.getStringList(configPath + ".lines");
// Scores zurücksetzen um Duplikate zu vermeiden
board.getEntries().forEach(board::resetScores);
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
String line = translate(player, lines.get(i)); String lineText = translate(player, lines.get(i));
// Verhindert das Verschwinden leerer Zeilen // Eindeutiger unsichtbarer Entry pro Zeile via Farb-Code
if (line.isEmpty() || line.trim().isEmpty()) { String entry = ChatColor.values()[i % ChatColor.values().length].toString()
line = "" + ChatColor.values()[i] + ChatColor.RESET; + ChatColor.RESET;
// getTeam statt registerNewTeam — verhindert "already in use" Fehler
Team team = board.getTeam("line_" + i);
if (team == null) team = board.registerNewTeam("line_" + i);
team.getEntries().forEach(team::removeEntry);
team.addEntry(entry);
// Text aufteilen falls länger als 64 Zeichen
if (lineText.length() > 64) {
team.setPrefix(lineText.substring(0, 64));
team.setSuffix(lineText.substring(64, Math.min(lineText.length(), 128)));
} else {
team.setPrefix(lineText);
team.setSuffix("");
} }
Score score = obj.getScore(line); // Score setzen + Zahl pro Eintrag zusätzlich ausblenden
org.bukkit.scoreboard.Score score = obj.getScore(entry);
score.setScore(lines.size() - i); score.setScore(lines.size() - i);
score.numberFormat(NumberFormat.blank());
} }
// Kein player.setScoreboard(board) nötig, da wir das Objekt 'board'
// direkt verändert haben, das der Spieler bereits hat.
} }
// --- Methoden für den /nexus sb Befehl --- // --- Methoden für den /nexus sb Befehl ---
@@ -103,37 +168,49 @@ public class ScoreboardModule implements Module {
public void setVisibility(Player player, boolean visible) { public void setVisibility(Player player, boolean visible) {
if (visible) { if (visible) {
hiddenPlayers.remove(player.getUniqueId()); hiddenPlayers.remove(player.getUniqueId());
player.sendMessage("§7[§6Nexus§7] §aScoreboard eingeschaltet."); // Wenn wir es wieder einschalten, ggf. Board zurücksetzen oder neu holen
if (!playerBoards.containsKey(player.getUniqueId())) {
playerBoards.put(player.getUniqueId(), Bukkit.getScoreboardManager().getNewScoreboard());
}
player.setScoreboard(playerBoards.get(player.getUniqueId()));
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aScoreboard eingeschaltet.");
updateSidebar(player); // Sofort aktualisieren
} else { } else {
hiddenPlayers.add(player.getUniqueId()); hiddenPlayers.add(player.getUniqueId());
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
player.sendMessage("§7[§6Nexus§7] §cScoreboard ausgeschaltet."); player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cScoreboard ausgeschaltet.");
} }
} }
public void setAdminMode(Player player, boolean admin) { public void setAdminMode(Player player, boolean admin) {
if (admin) { if (admin) {
adminModePlayers.add(player.getUniqueId()); adminModePlayers.add(player.getUniqueId());
player.sendMessage("§7[§6Nexus§7] §eModus: §cAdmin-Scoreboard §7(Owner-Sektion)"); player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§eModus: §cAdmin-Scoreboard §7(Owner-Sektion)");
} else { } else {
adminModePlayers.remove(player.getUniqueId()); adminModePlayers.remove(player.getUniqueId());
player.sendMessage("§7[§6Nexus§7] §eModus: §6Spieler-Scoreboard §7(Default-Sektion)"); player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§eModus: §6Spieler-Scoreboard §7(Default-Sektion)");
} }
// Sofortiges Update erzwingen
updateSidebar(player); updateSidebar(player);
} }
private String translate(Player player, String text) { private String translate(Player player, String text) {
String translated = PlaceholderAPI.setPlaceholders(player, text); String translated = text;
if (placeholderAPIEnabled) {
try {
translated = PlaceholderAPI.setPlaceholders(player, text);
} catch (NoClassDefFoundError ignored) {}
}
return ChatColor.translateAlternateColorCodes('&', translated); return ChatColor.translateAlternateColorCodes('&', translated);
} }
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
} }
hiddenPlayers.clear(); hiddenPlayers.clear();
adminModePlayers.clear(); adminModePlayers.clear();
playerBoards.clear(); // Speicher freigeben
} }
} }
@@ -1,24 +1,64 @@
package de.nexuslobby.modules.armorstandtools; package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.util.EulerAngle;
import java.util.Set; import java.util.UUID;
public class ASTListener implements Listener { public class ASTListener implements Listener {
private boolean hasArmorStandBypass(Player player) {
return player.isOp() || player.hasPermission("nexuslobby.armorstand.editbypass");
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onArmorStandManipulate(PlayerArmorStandManipulateEvent event) {
Player player = event.getPlayer();
if (hasArmorStandBypass(player)) {
return;
}
// Verhindert, dass Spieler Rüstung/Items aus ArmorStands nehmen oder austauschen.
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onArmorStandDamage(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof ArmorStand)) return;
Player player = null;
Entity damager = event.getDamager();
if (damager instanceof Player p) {
player = p;
} else if (damager instanceof Projectile projectile && projectile.getShooter() instanceof Player p) {
player = p;
}
if (player != null && hasArmorStandBypass(player)) {
return;
}
// Verhindert Abbauen/Beschädigen ohne Bypass, damit keine Equipment-Drops entnommen werden können.
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInteract(PlayerInteractAtEntityEvent event) { public void onInteract(PlayerInteractAtEntityEvent event) {
if (event.getHand() != EquipmentSlot.HAND) return; if (event.getHand() != EquipmentSlot.HAND) return;
@@ -26,7 +66,7 @@ public class ASTListener implements Listener {
Player p = event.getPlayer(); Player p = event.getPlayer();
// Sneak-Rechtsklick zum Bearbeiten // --- 1. FALL: SNEAKING -> Editor öffnen ---
if (p.isSneaking()) { if (p.isSneaking()) {
if (p.hasPermission("nexuslobby.armorstand.use")) { if (p.hasPermission("nexuslobby.armorstand.use")) {
event.setCancelled(true); event.setCancelled(true);
@@ -36,62 +76,160 @@ public class ASTListener implements Listener {
return; return;
} }
// Normale Interaktion (Befehl ausführen) // --- 2. FALL: NORMALER KLICK -> Dialog manuell triggern ---
Set<String> tags = as.getScoreboardTags(); // Dies triggert das Gruppensystem im ConversationManager
for (String tag : tags) { checkAndTriggerDialog(as, p);
// --- 3. FALL: Befehle ausführen (Bungee, etc.) ---
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("ascmd:")) { 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); event.setCancelled(true);
executeNexusCommand(p, tag);
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; break;
} }
} }
} }
/**
* Prüft, ob der ArmorStand Dialog-Tags hat und startet das Gespräch über den Manager.
* FIX: Liest jetzt alle Partner-Tags (conv_partner, conv_partner2, conv_partner3)
* und verwendet playConversationGroup statt nur die 2-NPC-Methode.
* Vorher wurden Gruppen-Gespräche mit 3-4 NPCs als 2-NPC-Gespräch ausgeführt.
*/
private void checkAndTriggerDialog(ArmorStand as, Player p) {
String groupName = null;
String partner1 = null, partner2 = null, partner3 = null;
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("conv_id:")) groupName = tag.split(":", 2)[1];
if (tag.startsWith("conv_partner:")) partner1 = tag.split(":", 2)[1];
if (tag.startsWith("conv_partner2:")) partner2 = tag.split(":", 2)[1];
if (tag.startsWith("conv_partner3:")) partner3 = tag.split(":", 2)[1];
}
if (groupName == null || partner1 == null) return;
try {
java.util.List<UUID> participants = new java.util.ArrayList<>();
participants.add(as.getUniqueId());
participants.add(UUID.fromString(partner1));
if (partner2 != null) participants.add(UUID.fromString(partner2));
if (partner3 != null) participants.add(UUID.fromString(partner3));
NexusLobby.getInstance().getConversationManager()
.playConversationGroup(participants, groupName);
} catch (Exception ignored) {
// Ungültige UUID oder fehlende Gruppe – still ignorieren
}
}
/**
* 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(de.nexuslobby.utils.LangManager.getPrefix() + "§7Du wirst mit §e" + server + " §7verbunden...");
}
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onInventoryClick(InventoryClickEvent event) { public void onInventoryClick(InventoryClickEvent event) {
if (!event.getView().getTitle().equals("Nexus ArmorStand Editor")) return; String title = event.getView().getTitle();
if (!title.equals("Nexus ArmorStand Editor") &&
!title.equals("Pose: Körperteil wählen") &&
!title.startsWith("Achsen:")) return;
// Amboss-GUI wird von AnvilRenameGUI selbst verwaltet — hier nicht anfassen
if (title.equals("§6Name ändern")) return;
event.setCancelled(true); event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player p)) return; if (!(event.getWhoClicked() instanceof Player p)) return;
ItemStack item = event.getCurrentItem();
if (item == null || item.getType() == Material.AIR) return;
ArmorStand as = AST.selectedArmorStand.get(p.getUniqueId()); ArmorStand as = AST.selectedArmorStand.get(p.getUniqueId());
if (as == null || !as.isValid()) { if (as == null || !as.isValid()) {
p.closeInventory(); p.closeInventory();
return; return;
} }
ItemStack item = event.getCurrentItem();
if (item == null || item.getType() == Material.AIR) return;
if (title.equals("Nexus ArmorStand Editor")) {
ArmorStandTool tool = ArmorStandTool.get(item); ArmorStandTool tool = ArmorStandTool.get(item);
if (tool != null) { if (tool != null) {
tool.execute(as, p); tool.execute(as, p);
// GUI aktualisieren, falls noch offen (SET_NAME schließt es z.B.) // Editor nur neu öffnen wenn kein Chat-Input-Modus aktiv ist
if (p.getOpenInventory().getTitle().equals("Nexus ArmorStand Editor")) { // und wir wirklich noch im Editor-Inventar sind
String newTitle = p.getOpenInventory().getTitle();
if (newTitle.equals("Nexus ArmorStand Editor")
&& !AST.chatInputMode.containsKey(p.getUniqueId())) {
new ArmorStandGUI(as, p); new ArmorStandGUI(as, p);
} }
} }
} }
else if (title.equals("Pose: Körperteil wählen")) {
private void executeNexusCommand(Player p, String tag) { if (item.getType() == Material.BARRIER) {
try { new ArmorStandGUI(as, p);
String[] parts = tag.split(":", 3); return;
if (parts.length < 3) return;
String type = parts[1].toLowerCase();
String finalizedCommand = parts[2].replace("%player%", p.getName());
switch (type) {
case "player" -> p.performCommand(finalizedCommand);
case "console" -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), finalizedCommand);
case "bungee" -> sendToBungee(p, finalizedCommand);
}
} catch (Exception e) { e.printStackTrace(); }
} }
private void sendToBungee(Player p, String server) { String targetPart = null;
ByteArrayDataOutput out = ByteStreams.newDataOutput(); switch (item.getType()) {
out.writeUTF("Connect"); case PLAYER_HEAD -> targetPart = "HEAD_ROT";
out.writeUTF(server); case IRON_CHESTPLATE -> targetPart = "BODY_ROT";
p.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", out.toByteArray()); case STICK -> targetPart = (event.getSlot() == 14) ? "L_ARM_ROT" : "R_ARM_ROT";
case LEATHER_BOOTS -> targetPart = (event.getSlot() == 28) ? "L_LEG_ROT" : "R_LEG_ROT";
}
if (targetPart != null) {
ArmorStandPoseGUI.openAxisDetailMenu(p, as, targetPart);
}
}
else if (title.startsWith("Achsen:")) {
if (item.getType() == Material.ARROW) {
ArmorStandPoseGUI.openPartSelectionMenu(p, as);
return;
}
String partName = title.split(": ")[1];
ArmorStandTool tool = ArmorStandTool.valueOf(partName);
double change = event.isShiftClick() ? Math.toRadians(1) : Math.toRadians(15);
if (event.isRightClick()) change *= -1;
EulerAngle oldPose = ArmorStandPoseGUI.getAngleForPart(as, tool);
EulerAngle newPose = oldPose;
if (item.getType() == Material.RED_DYE) {
newPose = oldPose.setX(oldPose.getX() + change);
} else if (item.getType() == Material.GREEN_DYE) {
newPose = oldPose.setY(oldPose.getY() + change);
} else if (item.getType() == Material.BLUE_DYE) {
newPose = oldPose.setZ(oldPose.getZ() + change);
}
ArmorStandPoseGUI.setAngleForPart(as, tool, newPose);
ArmorStandPoseGUI.openAxisDetailMenu(p, as, partName);
}
} }
} }
@@ -0,0 +1,95 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* Chat-basierte Namenseingabe für ArmorStands.
* Der Spieler tippt den neuen Namen in den Chat.
* Unterstützt & Farb-Codes.
*/
public class AnvilRenameGUI implements Listener {
private final Player player;
private final ArmorStand armorStand;
public AnvilRenameGUI(Player player, ArmorStand armorStand) {
this.player = player;
this.armorStand = armorStand;
// Listener registrieren
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
// Inventar schließen + Anweisung anzeigen
player.closeInventory();
// Title als UI-Feedback (wie AFK-Anzeige)
player.sendTitle(
"§6§lName eingeben",
"§7Schreibe den neuen Namen in den §eChat§7. §8(&-Codes erlaubt)",
10, 80, 20
);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§7Gib den neuen §eNamen §7in den Chat ein. §8(&-Farb-Codes erlaubt)");
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§c'abbrechen' §7eingeben zum Abbrechen.");
}
// ─────────────────────────────────────────────────────────────────────────
// Chat-Event — Paper 1.21 AsyncChatEvent
// ─────────────────────────────────────────────────────────────────────────
@EventHandler(priority = EventPriority.LOWEST)
public void onChat(AsyncChatEvent event) {
if (!event.getPlayer().equals(player)) return;
// Chat-Nachricht abfangen
event.setCancelled(true);
// Plain-Text aus Adventure Component extrahieren
String input = PlainTextComponentSerializer.plainText().serialize(event.message()).trim();
// Zurück auf Haupt-Thread wechseln für Bukkit-API Aufrufe
Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> {
HandlerList.unregisterAll(this);
AST.chatInputMode.remove(player.getUniqueId());
if (input.equalsIgnoreCase("abbrechen")) {
player.sendTitle("§c§lAbgebrochen", "", 5, 30, 10);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cNamensänderung abgebrochen.");
} else {
// & → § Farb-Codes
String newName = ChatColor.translateAlternateColorCodes('&', input);
armorStand.setCustomName(newName);
armorStand.setCustomNameVisible(!ChatColor.stripColor(newName).isEmpty());
player.sendTitle("§a§lName gesetzt!", "§r" + newName, 5, 40, 10);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aName gesetzt: " + newName);
}
// Zurück zum ArmorStand-Editor
if (armorStand.isValid()) {
new ArmorStandGUI(armorStand, player);
}
});
}
// ─────────────────────────────────────────────────────────────────────────
// Logout — Listener sauber abmelden
// ─────────────────────────────────────────────────────────────────────────
@EventHandler
public void onQuit(PlayerQuitEvent event) {
if (!event.getPlayer().equals(player)) return;
HandlerList.unregisterAll(this);
AST.chatInputMode.remove(player.getUniqueId());
}
}
@@ -1,98 +1,269 @@
package de.nexuslobby.modules.armorstandtools; package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Particle;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; 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 org.jetbrains.annotations.NotNull;
import java.util.ArrayList; 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 { public class ArmorStandCmdExecutor implements CommandExecutor {
private final String prefix = "§8[§6Nexus§8] "; private String prefix() { return de.nexuslobby.utils.LangManager.getPrefix(); }
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player p)) return true; if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.armorstand.cmd")) { if (!p.hasPermission("nexuslobby.armorstand.cmd")) {
p.sendMessage(prefix + "§cKeine Berechtigung!"); p.sendMessage(prefix() + de.nexuslobby.utils.LangManager.get("no_permission"));
return true; return true;
} }
// 1. Priorität: Name setzen (verwendet den in der Map gespeicherten AS aus der GUI) if (args.length == 0) return sendHelp(p);
if (args.length >= 2 && args[0].equalsIgnoreCase("name")) {
ArmorStand target = AST.selectedArmorStand.get(p.getUniqueId()); // --- CONVERSATION BEFEHLE ---
if (target == null || !target.isValid()) { if (args[0].equalsIgnoreCase("conv")) {
p.sendMessage(prefix + "§cBitte wähle zuerst einen ArmorStand im GUI (Sneak-Rechtsklick) aus!"); 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; 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); 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")) { if (nameInput.equalsIgnoreCase("none")) {
target.setCustomName(""); target.setCustomName("");
target.setCustomNameVisible(false); target.setCustomNameVisible(false);
p.sendMessage(prefix + "§eName entfernt."); p.sendMessage(prefix() + de.nexuslobby.utils.LangManager.get("name_removed"));
} else { } else {
String colored = ChatColor.translateAlternateColorCodes('&', nameInput); String colored = ChatColor.translateAlternateColorCodes('&', nameInput);
target.setCustomName(colored); target.setCustomName(colored);
target.setCustomNameVisible(true); 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; return true;
} }
// 2. Priorität: Action-Commands (verwendet Nearby-Suche für /nexuscmd add...) if (args[0].equalsIgnoreCase("add") && args.length >= 5) {
ArmorStand target = getNearbyArmorStand(p); if (target == null) { p.sendMessage(prefix() + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; }
if (target == null) { String slot1 = args[1], slot2 = args[2], type = args[3].toLowerCase();
p.sendMessage(prefix + "§cKein ArmorStand in der Nähe (4 Blöcke) gefunden!"); String cmdStr = buildString(args, 4);
target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr);
p.sendMessage(prefix() + de.nexuslobby.utils.LangManager.get("command_bound"));
return true; return true;
} }
if (args.length >= 5 && args[0].equalsIgnoreCase("add")) { if (args[0].equalsIgnoreCase("list")) {
String type = args[3].toLowerCase(); if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; }
String cmd = buildString(args, 4); p.sendMessage(de.nexuslobby.utils.LangManager.get("commands_and_tags"));
target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t));
if (!type.equals("player") && !type.equals("console") && !type.equals("bungee")) {
p.sendMessage(prefix + "§cTypen: §eplayer, console, bungee");
return true; return true;
} }
target.addScoreboardTag("ascmd:" + type + ":" + cmd); if (args[0].equalsIgnoreCase("remove")) {
p.sendMessage(prefix + "§aBefehl an ArmorStand gebunden!"); 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 true;
} }
if (args.length >= 1 && args[0].equalsIgnoreCase("list")) { if (args[0].equalsIgnoreCase("clearbubbles")) {
p.sendMessage("§6§lBefehle auf diesem ArmorStand:"); ConversationManager cm = NexusLobby.getInstance().getConversationManager();
for (String tag : target.getScoreboardTags()) { if (cm == null) {
if (tag.startsWith("ascmd:")) { p.sendMessage(prefix() + "§cConversationManager ist nicht aktiv.");
p.sendMessage(" §8» §e" + tag.replace("ascmd:", ""));
}
}
return true; return true;
} }
cm.clearHangingBubbles();
if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) { p.sendMessage(prefix() + "§aAlle hängenden Sprechblasen wurden entfernt.");
Set<String> tags = target.getScoreboardTags(); p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
for (String tag : new ArrayList<>(tags)) {
if (tag.startsWith("ascmd:")) target.removeScoreboardTag(tag);
}
p.sendMessage(prefix + "§eAlle Befehle entfernt.");
return true; return true;
} }
return sendHelp(p); return sendHelp(p);
} }
private ArmorStand getNearbyArmorStand(Player p) { private ArmorStand getTargetArmorStand(Player p) {
for (Entity e : p.getNearbyEntities(4, 4, 4)) { RayTraceResult result = p.getWorld().rayTraceEntities(
if (e instanceof ArmorStand as) return as; p.getEyeLocation(),
p.getLocation().getDirection(),
8,
0.3,
entity -> entity instanceof ArmorStand
);
if (result != null && result.getHitEntity() instanceof ArmorStand as) {
return as;
} }
return null; return null;
} }
@@ -105,12 +276,27 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
return sb.toString(); 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) { private boolean sendHelp(Player p) {
p.sendMessage("§6§lNexus Command-Binder Hilfe:"); p.sendMessage("§6§lNexus Tools Hilfe:");
p.sendMessage("§e/nexuscmd name <Text> §7- Namen setzen (AS vorher anklicken)"); p.sendMessage("§e/nexuscmd name <Text> §7- Setzt Namen (Bedrock-Safe)");
p.sendMessage("§e/nexuscmd add 0 0 <type> <cmd> §7- Befehl binden"); p.sendMessage("§e/nexuscmd say <Text> §7- Erstellt eine Sprechblase");
p.sendMessage("§e/nexuscmd list §7- Befehle anzeigen"); p.sendMessage("§e/nexuscmd lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/nexuscmd remove §7- Befehle löschen"); 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");
p.sendMessage("§e/nexuscmd clearbubbles §7- Entfernt hängende Sprechblasen");
return true; return true;
} }
} }
@@ -1,95 +1,191 @@
package de.nexuslobby.modules.armorstandtools; package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Particle;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.EulerAngle;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Set; import java.util.UUID;
/**
* ArmorStandCommand - Vollständige Steuerung für ArmorStands.
* Inklusive Dynamic-Modus, Look-At Funktion, Befehls-Slots und Conversation-Sprecher.
*/
public class ArmorStandCommand implements CommandExecutor { 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 @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player p)) return true; if (!(sender instanceof Player p)) {
sender.sendMessage("Nur Spieler können diesen Befehl ausführen.");
if (!p.hasPermission("nexuslobby.armorstand.use")) {
p.sendMessage(Config.prefix() + ChatColor.RED + Config.generalNoPerm);
return true; return true;
} }
// Suche ArmorStand im Umkreis von 5 Blöcken // Fester Prefix für maximale Build-Sicherheit
ArmorStand target = getNearbyArmorStand(p); String prefix = de.nexuslobby.utils.LangManager.getPrefix();
// Fall 1: Nur /astools -> GUI öffnen if (!p.hasPermission("nexuslobby.armorstand.use")) {
p.sendMessage(prefix + ChatColor.RED + "Dazu hast du keine Rechte!");
return true;
}
// 1. Suche nach dem ArmorStand (Präzise & Umkreis)
ArmorStand target = getBestTargetArmorStand(p);
// Fall: Nur /astools oder /nt (Ohne Argumente)
if (args.length == 0) { if (args.length == 0) {
if (target != null) { if (target != null) {
AST.selectedArmorStand.put(p.getUniqueId(), target); AST.selectedArmorStand.put(p.getUniqueId(), target);
new ArmorStandGUI(target, p); new ArmorStandGUI(target, p);
p.sendMessage(prefix + "§aEditor für ArmorStand geöffnet.");
} else { } else {
p.sendMessage(Config.prefix() + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!"); p.sendMessage(prefix + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!");
} }
return true; return true;
} }
// Fall 2: Unterbefehle (add, remove, list)
String sub = args[0].toLowerCase(); String sub = args[0].toLowerCase();
// Globaler Reload
if (sub.equals("reload")) {
NexusLobby.getInstance().reloadPlugin();
p.sendMessage(prefix + "§aKonfiguration und Module wurden neu geladen.");
return true;
}
// Für alle anderen Aktionen ist ein ArmorStand zwingend erforderlich
if (target == null) { if (target == null) {
p.sendMessage(Config.prefix() + ChatColor.RED + "Du musst nah an einem ArmorStand stehen!"); p.sendMessage(prefix + ChatColor.RED + "Du musst einen ArmorStand anschauen oder direkt davor stehen!");
return true; return true;
} }
switch (sub) { switch (sub) {
case "addplayer": // /astools addplayer <command> case "dynamic":
if (args.length < 2) return sendHelp(p); if (!p.hasPermission("nexuslobby.armorstand.dynamic")) {
String pCmd = buildString(args, 1); p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Dynamic-NPCs!");
target.addScoreboardTag("ascmd:player:" + pCmd); return true;
p.sendMessage(Config.prefix() + "§aBefehl (Player) hinzugefügt: §e" + pCmd); }
if (NexusLobby.getInstance().getDynamicArmorStandModule() != null) {
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.");
p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 25, 0.5, 0.5, 0.5, 0.1);
}
NexusLobby.getInstance().getDynamicArmorStandModule().toggleDynamicStatus(target);
} else {
p.sendMessage(prefix + "§cFehler: Dynamic-Modul ist nicht aktiv!");
}
break; break;
case "addconsole": // /astools addconsole <command> case "lookat":
if (args.length < 2) return sendHelp(p); if (!p.hasPermission("nexuslobby.armorstand.lookat")) {
String cCmd = buildString(args, 1); p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Look-At!");
target.addScoreboardTag("ascmd:console:" + cCmd); return true;
p.sendMessage(Config.prefix() + "§aBefehl (Konsole) hinzugefügt: §e" + cCmd); }
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; break;
case "addbungee": // /astools addbungee <server> case "addplayer":
if (args.length < 2) return sendHelp(p); if (args.length < 4) return sendHelp(p, prefix);
String server = args[1]; String s1P = args[1];
target.addScoreboardTag("ascmd:bungee:" + server); String s2P = args[2];
p.sendMessage(Config.prefix() + "§aBefehl (Bungee) hinzugefügt: §eConnect zu " + server); String pCmd = buildString(args, 3);
target.addScoreboardTag("ascmd:" + s1P + ":" + s2P + ":player:" + pCmd);
p.sendMessage(prefix + "§aBefehl (Player) auf Slot " + s1P + "/" + s2P + " gespeichert.");
break; break;
case "remove": // /astools remove (Löscht alle ascmd Tags) case "addconsole":
Set<String> tags = target.getScoreboardTags(); if (args.length < 4) return sendHelp(p, prefix);
tags.removeIf(tag -> tag.startsWith("ascmd:")); String s1C = args[1];
p.sendMessage(Config.prefix() + "§cAlle Befehle von diesem ArmorStand entfernt."); 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; break;
case "reload": case "remove":
Config.load(); target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
p.sendMessage(Config.prefix() + "§aKonfiguration neu geladen."); p.sendMessage(prefix + "§cAlle Befehls-Tags wurden entfernt.");
break; break;
default: default:
sendHelp(p); sendHelp(p, prefix);
break; break;
} }
return true; return true;
} }
private ArmorStand getNearbyArmorStand(Player p) { private ArmorStand getBestTargetArmorStand(Player p) {
for (Entity e : p.getNearbyEntities(5, 5, 5)) { ArmorStand best = null;
if (e instanceof ArmorStand as) return as; double bestDot = 0.95;
for (Entity e : p.getNearbyEntities(8, 8, 8)) {
if (e instanceof ArmorStand as) {
double dot = p.getLocation().getDirection().dot(
as.getLocation().toVector().subtract(p.getLocation().toVector()).normalize()
);
if (dot > bestDot) {
bestDot = dot;
best = as;
} }
return null; }
}
if (best == null) {
for (Entity e : p.getNearbyEntities(4, 4, 4)) {
if (e instanceof ArmorStand as) {
return as;
}
}
}
return best;
} }
private String buildString(String[] args, int start) { private String buildString(String[] args, int start) {
@@ -100,14 +196,16 @@ public class ArmorStandCommand implements CommandExecutor {
return sb.toString(); return sb.toString();
} }
private boolean sendHelp(Player p) { private boolean sendHelp(Player p, String prefix) {
p.sendMessage("§6§lArmorStandTools Hilfe:"); p.sendMessage(" ");
p.sendMessage("§e/astools §7- Öffnet Editor"); p.sendMessage("§8§m-----------§r " + prefix + "§8§m-----------");
p.sendMessage("§e/astools addplayer <cmd> §7- Befehl als Spieler ausführen"); p.sendMessage("§e/astools §7- Editor GUI öffnen");
p.sendMessage("§e/astools addconsole <cmd> §7- Befehl als Konsole ausführen"); p.sendMessage("§b/astools dynamic §7- Näherung/Zeit Logik");
p.sendMessage("§e/astools addbungee <server> §7- Serverwechsel"); p.sendMessage("§b/astools lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/astools remove §7- Entfernt alle Befehle"); p.sendMessage("§e/astools addplayer <S1> <S2> <cmd> §7- Befehl (Spieler)");
p.sendMessage("§e/astools reload §7- Lädt Config neu"); p.sendMessage("§e/astools addconsole <S1> <S2> <cmd> §7- Befehl (Konsole)");
p.sendMessage("§e/astools remove §7- Befehle löschen");
p.sendMessage("§8§m---------------------------------------");
return true; return true;
} }
} }
@@ -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;
}
}
@@ -0,0 +1,111 @@
package de.nexuslobby.modules.armorstandtools;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.util.EulerAngle;
import java.util.Arrays;
public class ArmorStandPoseGUI {
// Schritt 1: Auswahl welches Körperteil (Die "letzte Reihe" ist jetzt hier)
public static void openPartSelectionMenu(Player p, ArmorStand as) {
Inventory inv = Bukkit.createInventory(null, 45, "Pose: Körperteil wählen");
ItemStack filler = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
for (int i = 0; i < 45; i++) inv.setItem(i, filler);
inv.setItem(10, createPartIcon(Material.PLAYER_HEAD, "§eKopf", as.getHeadPose()));
inv.setItem(12, createPartIcon(Material.IRON_CHESTPLATE, "§eTorso", as.getBodyPose()));
inv.setItem(14, createPartIcon(Material.STICK, "§eLinker Arm", as.getLeftArmPose()));
inv.setItem(16, createPartIcon(Material.STICK, "§eRechter Arm", as.getRightArmPose()));
inv.setItem(28, createPartIcon(Material.LEATHER_BOOTS, "§eLinkes Bein", as.getLeftLegPose()));
inv.setItem(30, createPartIcon(Material.LEATHER_BOOTS, "§eRechtes Bein", as.getRightLegPose()));
inv.setItem(40, createNamedItem(Material.BARRIER, "§cZurück zum Hauptmenü"));
p.openInventory(inv);
}
// Schritt 2: Detail-Einstellung der Achsen (X, Y, Z)
public static void openAxisDetailMenu(Player p, ArmorStand as, String partName) {
Inventory inv = Bukkit.createInventory(null, 27, "Achsen: " + partName);
ItemStack filler = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
for (int i = 0; i < 27; i++) inv.setItem(i, filler);
ArmorStandTool tool = ArmorStandTool.valueOf(partName);
EulerAngle angle = getAngleForPart(as, tool);
inv.setItem(11, createAxisItem(Material.RED_DYE, "§c§lX-Achse (Pitch)", angle.getX()));
inv.setItem(13, createAxisItem(Material.GREEN_DYE, "§a§lY-Achse (Yaw)", angle.getY()));
inv.setItem(15, createAxisItem(Material.BLUE_DYE, "§b§lZ-Achse (Roll)", angle.getZ()));
inv.setItem(22, createNamedItem(Material.ARROW, "§7Zurück zur Auswahl"));
p.openInventory(inv);
}
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:",
"§7X: §f" + Math.round(Math.toDegrees(angle.getX())) + "°",
"§7Y: §f" + Math.round(Math.toDegrees(angle.getY())) + "°",
"§7Z: §f" + Math.round(Math.toDegrees(angle.getZ())) + "°",
"", "§6Klicken zum Justieren"
));
item.setItemMeta(meta);
return item;
}
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)) + "°",
"",
"§eLinksklick: §f+15° §8| §eRechtsklick: §f-15°",
"§6Shift-Klick: §f+1° §8| §6Shift-Rechts: §f-1°"
));
item.setItemMeta(meta);
return item;
}
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;
}
public static EulerAngle getAngleForPart(ArmorStand as, ArmorStandTool part) {
return switch (part) {
case HEAD_ROT -> as.getHeadPose();
case BODY_ROT -> as.getBodyPose();
case L_ARM_ROT -> as.getLeftArmPose();
case R_ARM_ROT -> as.getRightArmPose();
case L_LEG_ROT -> as.getLeftLegPose();
case R_LEG_ROT -> as.getRightLegPose();
default -> EulerAngle.ZERO;
};
}
public static void setAngleForPart(ArmorStand as, ArmorStandTool part, EulerAngle angle) {
switch (part) {
case HEAD_ROT -> as.setHeadPose(angle);
case BODY_ROT -> as.setBodyPose(angle);
case L_ARM_ROT -> as.setLeftArmPose(angle);
case R_ARM_ROT -> as.setRightArmPose(angle);
case L_LEG_ROT -> as.setLeftLegPose(angle);
case R_LEG_ROT -> as.setRightLegPose(angle);
}
}
}
@@ -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;
}
}
@@ -6,84 +6,33 @@ import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import de.nexuslobby.NexusLobby;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public enum ArmorStandTool { public enum ArmorStandTool {
// Sichtbarkeit & Basis-Attribute INVIS(Material.GLASS_PANE, 10),
INVIS(Material.GLASS_PANE, 10) { ARMS(Material.STICK, 11),
@Override public void execute(ArmorStand as, Player p) { as.setVisible(!as.isVisible()); } BASE(Material.STONE_SLAB, 12),
}, SIZE(Material.PUMPKIN_SEEDS, 13),
ARMS(Material.STICK, 11) { GRAV(Material.ANVIL, 14),
@Override public void execute(ArmorStand as, Player p) { as.setArms(!as.hasArms()); } INVUL(Material.BEDROCK, 15),
}, SET_NAME(Material.NAME_TAG, 16),
BASE(Material.STONE_SLAB, 12) {
@Override public void execute(ArmorStand as, Player p) { as.setBasePlate(!as.hasBasePlate()); }
},
SIZE(Material.PUMPKIN_SEEDS, 13) {
@Override public void execute(ArmorStand as, Player p) { as.setSmall(!as.isSmall()); }
},
GRAV(Material.ANVIL, 14) {
@Override public void execute(ArmorStand as, Player p) { as.setGravity(!as.hasGravity()); }
},
INVUL(Material.BEDROCK, 15) {
@Override public void execute(ArmorStand as, Player p) { as.setInvulnerable(!as.isInvulnerable()); }
},
// Hologramm / Name setzen // Neuer Button für Gespräche
SET_NAME(Material.NAME_TAG, 16) { CONV_SETUP(Material.WRITABLE_BOOK, 17),
@Override public void execute(ArmorStand as, Player p) {
p.closeInventory();
p.sendMessage(" ");
p.sendMessage("§8§m--------------------------------------");
p.sendMessage("§6§lHologramm-Editor");
p.sendMessage("§7Nutze: §e/nexuscmd name <Text> §7um den Namen zu setzen.");
p.sendMessage("§7Nutze: §e/nexuscmd name none §7um den Namen zu entfernen.");
p.sendMessage("§7Farbcodes mit §6& §7werden unterstützt.");
p.sendMessage("§8§m--------------------------------------");
AST.selectedArmorStand.put(p.getUniqueId(), as);
}
},
// KÖRPERGESTALTUNG (Rotationen) OPEN_POSE_EDITOR(Material.ARMOR_STAND, 31),
HEAD_ROT(Material.PLAYER_HEAD, 28) {
@Override public void execute(ArmorStand as, Player p) {
as.setHeadPose(as.getHeadPose().add(Math.toRadians(15), 0, 0));
}
},
BODY_ROT(Material.IRON_CHESTPLATE, 29) {
@Override public void execute(ArmorStand as, Player p) {
as.setBodyPose(as.getBodyPose().add(0, Math.toRadians(15), 0));
}
},
L_ARM_ROT(Material.STICK, 30) {
@Override public void execute(ArmorStand as, Player p) {
as.setLeftArmPose(as.getLeftArmPose().add(Math.toRadians(15), 0, 0));
}
},
R_ARM_ROT(Material.STICK, 31) {
@Override public void execute(ArmorStand as, Player p) {
as.setRightArmPose(as.getRightArmPose().add(Math.toRadians(15), 0, 0));
}
},
L_LEG_ROT(Material.LEATHER_BOOTS, 32) {
@Override public void execute(ArmorStand as, Player p) {
as.setLeftLegPose(as.getLeftLegPose().add(Math.toRadians(15), 0, 0));
}
},
R_LEG_ROT(Material.LEATHER_BOOTS, 33) {
@Override public void execute(ArmorStand as, Player p) {
as.setRightLegPose(as.getRightLegPose().add(Math.toRadians(15), 0, 0));
}
},
REMOVE(Material.BARRIER, 40) { HEAD_ROT(Material.PLAYER_HEAD, -1),
@Override public void execute(ArmorStand as, Player p) { BODY_ROT(Material.IRON_CHESTPLATE, -1),
as.remove(); L_ARM_ROT(Material.STICK, -1),
p.closeInventory(); R_ARM_ROT(Material.STICK, -1),
p.sendMessage("§cNexus ArmorStand entfernt."); L_LEG_ROT(Material.LEATHER_BOOTS, -1),
} R_LEG_ROT(Material.LEATHER_BOOTS, -1),
};
REMOVE(Material.BARRIER, 40);
private final Material material; private final Material material;
private final int slot; private final int slot;
@@ -93,20 +42,56 @@ public enum ArmorStandTool {
this.slot = slot; this.slot = slot;
} }
public abstract void execute(ArmorStand as, Player p); public void execute(ArmorStand as, Player p) {
switch (this) {
case INVIS -> as.setVisible(!as.isVisible());
case ARMS -> as.setArms(!as.hasArms());
case BASE -> as.setBasePlate(!as.hasBasePlate());
case SIZE -> as.setSmall(!as.isSmall());
case GRAV -> as.setGravity(!as.hasGravity());
case INVUL -> as.setInvulnerable(!as.isInvulnerable());
case SET_NAME -> {
AST.selectedArmorStand.put(p.getUniqueId(), as);
AST.chatInputMode.put(p.getUniqueId(), as);
new AnvilRenameGUI(p, as); // schließt Inventar intern
}
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(de.nexuslobby.utils.LangManager.getPrefix() + "§aNPC als Sprecher 1 markiert.");
} else {
p.setMetadata("conv_npc2", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString()));
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aNPC als Sprecher 2 markiert.");
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§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 -> {}
}
}
public int getSlot() { return slot; } public int getSlot() { return slot; }
public boolean isForGui() { return slot != -1; }
// Diese Methode hat im letzten Code gefehlt:
public boolean isForGui() { return true; }
public ItemStack updateLore(ArmorStand as) { public ItemStack updateLore(ArmorStand as) {
ItemStack item = new ItemStack(material); ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta != null) { if (meta != null) {
meta.setDisplayName("§6" + this.name().replace("_", " ")); 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<>(); List<String> lore = new ArrayList<>();
lore.add("§7Klicken zum Bearbeiten"); lore.add("§7Klicken zum Verwalten");
meta.setLore(lore); meta.setLore(lore);
item.setItemMeta(meta); item.setItemMeta(meta);
} }
@@ -114,8 +99,13 @@ public enum ArmorStandTool {
} }
public static ArmorStandTool get(ItemStack item) { public static ArmorStandTool get(ItemStack item) {
if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return null; if (item == null || !item.hasItemMeta()) return null;
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace(" ", "_"); ItemMeta meta = item.getItemMeta();
// FIX: getItemMeta() kann null zurückgeben; hasDisplayName() vor getDisplayName() prüfen
if (meta == null || !meta.hasDisplayName()) return null;
String name = ChatColor.stripColor(meta.getDisplayName()).replace(" ", "_");
if (name.equals("Pose_Editor_öffnen")) return OPEN_POSE_EDITOR;
if (name.equals("Gesprächs-Setup")) return CONV_SETUP;
try { return valueOf(name); } catch (Exception e) { return null; } try { return valueOf(name); } catch (Exception e) { return null; }
} }
} }
@@ -0,0 +1,297 @@
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<>();
// Alle aktiven Bubble-Entities tracken – für zuverlässiges Cleanup bei Reload/Crash
private final Set<UUID> activeBubbles = 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.
* FIX: Nur ArmorStands mit dem Tag "nexus_bubble" werden entfernt.
* Die alte Bedingung (!isVisible && isMarker && customName != null) war
* zu breit und hätte auch legitime Marker-ArmorStands anderer Systeme gelöscht.
*/
public void clearHangingBubbles() {
// Zuerst: Alle getrackte Bubble-Entities direkt entfernen (schnell, kein World-Scan nötig)
for (UUID uuid : new HashSet<>(activeBubbles)) {
Entity e = Bukkit.getEntity(uuid);
if (e != null) e.remove();
}
activeBubbles.clear();
// Danach: Vollständiger World-Scan als Fallback (fängt Bubbles nach Crash/Reload ab)
int count = 0;
for (World world : Bukkit.getWorlds()) {
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
if (as.getScoreboardTags().contains("nexus_bubble")) {
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.setPersistent(false); // Überlebt keinen Server-Neustart/-Absturz
s.addScoreboardTag("nexus_bubble");
});
activeBubbles.add(bubble.getUniqueId());
Bukkit.getScheduler().runTaskLater(plugin, () -> {
bubble.remove();
activeBubbles.remove(bubble.getUniqueId());
}, 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() {
clearHangingBubbles(); // Erst aufräumen, dann neu laden
activeSpeakers.clear();
setupFile();
}
}
@@ -0,0 +1,131 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.EulerAngle;
public class DynamicArmorStandModule implements Module {
private final NamespacedKey npcKey = new NamespacedKey(NexusLobby.getInstance(), "dynamic_npc");
@Override
public String getName() {
return "DynamicArmorStands";
}
@Override
public void onEnable() {
startUpdateTask();
}
private void startUpdateTask() {
// Alle 5 Ticks (0.25s) für flüssige Partikel
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::updateAllArmorStands, 20L, 5L);
}
public void updateAllArmorStands() {
for (World world : Bukkit.getWorlds()) {
long time = world.getTime();
boolean isIngameNight = (time >= 13000 && time <= 23000);
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand as) {
if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE) ||
as.getScoreboardTags().contains("as_dynamic")) {
applyDynamicChanges(as, isIngameNight);
if (isIngameNight && as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
spawnStarParticles(as);
}
}
}
}
}
}
private void applyDynamicChanges(ArmorStand as, boolean isNight) {
if (!as.hasArms()) as.setArms(true);
// --- SPIELER-NÄHE ---
boolean playerNearby = false;
for (Entity nearby : as.getNearbyEntities(2.0, 2.0, 2.0)) {
if (nearby instanceof Player) { playerNearby = true; break; }
}
if (playerNearby) {
if (as.getEquipment().getItemInMainHand().getType() != Material.STONE_SWORD) {
as.getEquipment().setItemInMainHand(new ItemStack(Material.STONE_SWORD));
as.setRightArmPose(new EulerAngle(Math.toRadians(-80), Math.toRadians(-10), 0));
}
} else {
if (as.getEquipment().getItemInMainHand().getType() == Material.STONE_SWORD) {
as.getEquipment().setItemInMainHand(null);
as.setRightArmPose(EulerAngle.ZERO);
}
}
// --- INGAME-ZEIT ---
if (isNight) {
if (as.getEquipment().getItemInOffHand().getType() != Material.TORCH) {
as.getEquipment().setItemInOffHand(new ItemStack(Material.TORCH));
as.setLeftArmPose(new EulerAngle(Math.toRadians(-50), Math.toRadians(15), 0));
}
} else {
if (as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
as.getEquipment().setItemInOffHand(null);
as.setLeftArmPose(EulerAngle.ZERO);
}
}
}
private void spawnStarParticles(ArmorStand as) {
Location loc = as.getLocation();
double yawRad = Math.toRadians(loc.getYaw());
// Vektoren für Blickrichtung und Seite
double dirX = -Math.sin(yawRad);
double dirZ = Math.cos(yawRad);
double sideX = Math.cos(yawRad);
double sideZ = Math.sin(yawRad);
// Position: Vorne (0.4), Links (0.5), Oben (1.45)
double finalX = loc.getX() + (dirX * 0.8) + (sideX * 0.2);
double finalY = loc.getY() + 1.45;
double finalZ = loc.getZ() + (dirZ * 0.8) + (sideZ * 0.2);
Location particleLoc = new Location(as.getWorld(), finalX, finalY, finalZ);
as.getWorld().spawnParticle(Particle.WAX_OFF, particleLoc, 2, 0.05, 0.05, 0.05, 0.01);
}
public void toggleDynamicStatus(ArmorStand as) {
if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE)) {
as.getPersistentDataContainer().remove(npcKey);
as.removeScoreboardTag("as_dynamic");
as.getEquipment().setItemInMainHand(null);
as.getEquipment().setItemInOffHand(null);
as.setRightArmPose(EulerAngle.ZERO);
as.setLeftArmPose(EulerAngle.ZERO);
} else {
as.getPersistentDataContainer().set(npcKey, PersistentDataType.BYTE, (byte) 1);
as.addScoreboardTag("as_dynamic");
applyDynamicChanges(as, (as.getWorld().getTime() >= 13000 && as.getWorld().getTime() <= 23000));
}
}
@Override
public void onDisable() {
// Bereinigung falls nötig
}
}
@@ -0,0 +1,795 @@
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.ConfigurationSection;
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.event.player.PlayerJoinEvent;
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.*;
public class SoccerModule implements Module, Listener, CommandExecutor {
// =========================================================================
// 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";
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;
// Verhindert dass Dribbling einen Kick sofort überschreibt.
private static final long KICK_DRIBBLE_LOCK_MS = 300L;
private static final double PARTICLE_SPEED_HIGH = 0.85;
private static final double PARTICLE_SPEED_MEDIUM = 0.45;
private static final double PARTICLE_SPEED_MIN = 0.05;
// Tor-Erkennung: Schrittweite beim Segment-Check in Blöcken.
// 0.1 → bei 1.35 b/t Kick-Speed ~14 Prüfpunkte pro Tick. Sehr sicher.
private static final double CHECK_STEP = 0.1;
// ── Tor-Partikel ──────────────────────────────────────────────────────────
private static final Particle.DustOptions DUST_TEAM1 = new Particle.DustOptions(Color.fromRGB(30, 120, 255), 1.2f);
private static final Particle.DustOptions DUST_TEAM2 = new Particle.DustOptions(Color.fromRGB(255, 50, 50), 1.2f);
private static final Particle.DustOptions DUST_GOAL_FLASH = new Particle.DustOptions(Color.YELLOW, 2.0f);
private static final double PARTICLE_STEP = 0.35;
private static final long GOAL_RESPAWN_DELAY = 80L;
private static final long GOAL_COOLDOWN_MS = 3_000L;
// =========================================================================
// LAUFZEIT-ZUSTAND
// =========================================================================
private ArmorStand ball;
private Location spawnLocation;
private long lastMoveTime;
private long lastGoalTime = 0L;
private long lastKickTime = 0L;
// ── Tor-Erkennung: Position aus dem VORHERIGEN Tick ───────────────────────
//
// WARUM DAS FUNKTIONIERT:
// getLocation() eines ArmorStands liefert in Tick N die Position aus Tick N-1.
// Das klingt wie ein Problem, ist aber tatsächlich die Lösung:
//
// Wenn wir in jedem Tick:
// 1. prevPos = die aktuelle getLocation() merken (= echte Position aus letztem Tick)
// 2. Am ENDE des Ticks: currPos = getLocation() lesen
// 3. Das Segment prevPos→currPos prüfen
//
// Dann deckt dieses Segment exakt den Weg ab den der Ball in Minecraft
// in den letzten 50ms zurückgelegt hat – egal wie schnell, egal ob Kick
// oder Dribble oder geschoben.
//
// WICHTIG: prevPos wird am ANFANG des Ticks gesetzt (bevor Physik-Berechnungen
// die Velocity ändern), currPos am ENDE. So ist das Segment vollständig.
private Location prevPos = null;
private final Map<String, SoccerGoal> goals = new LinkedHashMap<>();
private final Map<Integer, Integer> scores = new HashMap<>();
private final Map<UUID, Location> selPos1 = new HashMap<>();
private final Map<UUID, Location> selPos2 = new HashMap<>();
// =========================================================================
// MODULE-LIFECYCLE
// =========================================================================
@Override public String getName() { return "Soccer"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
loadConfigLocation();
loadGoals();
scores.put(1, 0);
scores.put(2, 0);
removeAllOldBalls();
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
removeAllOldBalls();
spawnBall();
}, 40L);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBalls, 100L);
// ── Physik-Tick ────────────────────────────────────────────────────────
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
if (ball != null && ball.isValid()) removeDuplicateBalls();
if (ball == null || !ball.isValid()) return;
// ── SCHRITT 1: Aktuelle Position als "vorherige" speichern ─────────
// getLocation() ist hier die Position aus dem letzten Server-Tick –
// d.h. die Position BEVOR die Physik dieses Ticks berechnet wurde.
Location currentPos = ball.getLocation().clone();
// ── SCHRITT 2: Physik und Spieler-Interaktion verarbeiten ──────────
Vector vel = ball.getVelocity();
double speed = vel.length();
handleWallBounce(vel);
handleParticles(speed);
handleDribbling();
// ── SCHRITT 3: Nach Physik die neue Position lesen ─────────────────
// Minecraft wendet nach setVelocity noch Drag (0.98 horizontal, Gravity ~0.08
// + 0.98 vertikal) an. Wir approximieren: newPos = currentPos + vel * 0.98
// Das ist deutlich genauer als currentPos + vel und vermeidet 1-Tick-Versatz
// bei der Tor-Erkennung nach schnellen Kicks.
Location newPos = currentPos.clone().add(vel.clone().multiply(0.98));
// ── SCHRITT 4: Segment prüfen ─────────────────────────────────────
// Das Segment prevPos → newPos deckt den vollständigen Weg ab den
// der Ball seit dem letzten Tor-Check zurückgelegt hat.
if (!goals.isEmpty()
&& System.currentTimeMillis() - lastGoalTime >= GOAL_COOLDOWN_MS
&& prevPos != null) {
checkSegment(prevPos, newPos, currentPos.getWorld());
}
// ── SCHRITT 5: prevPos für nächsten Tick aktualisieren ─────────────
prevPos = newPos.clone();
// ── Respawn-Check ──────────────────────────────────────────────────
long respawnDelayMs = NexusLobby.getInstance().getConfig()
.getLong("ball.respawn_delay", 60) * 1_000L;
if (System.currentTimeMillis() - lastMoveTime > respawnDelayMs
|| ball.getLocation().getY() < VOID_THRESHOLD) {
respawnBall();
}
}, 1L, 1L);
// ── Tor-Partikel ──────────────────────────────────────────────────────
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(),
this::drawAllGoalParticles, 4L, 4L);
}
@Override
public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
if (ball != null && ball.isValid()) { ball.remove(); ball = null; }
}
// =========================================================================
// TOR-ERKENNUNG
// =========================================================================
/**
* Prüft das Segment von {@code from} nach {@code to} auf Tor-Treffer.
*
* Das Segment wird in CHECK_STEP (0.1 Block) Schritte unterteilt.
* An jedem Punkt werden drei Y-Höhen geprüft (Fuß / Mitte / Kopf des Balls).
*
* @param from Position am Anfang des Ticks
* @param to Position am Ende des Ticks (= from + velocity)
* @param world Welt des Balls
*/
private void checkSegment(Location from, Location to, World world) {
if (world == null) return;
double dx = to.getX() - from.getX();
double dy = to.getY() - from.getY();
double dz = to.getZ() - from.getZ();
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
// Ball steht fast still → nur aktuellen Punkt prüfen
if (len < 0.001) {
checkPoint(from.getX(), from.getY(), from.getZ(), world);
return;
}
int steps = Math.max(1, (int) Math.ceil(len / CHECK_STEP));
double sx = dx / steps;
double sy = dy / steps;
double sz = dz / steps;
for (int i = 0; i <= steps; i++) {
double x = from.getX() + sx * i;
double y = from.getY() + sy * i;
double z = from.getZ() + sz * i;
if (checkPoint(x, y, z, world)) return;
}
}
/**
* Prüft einen Punkt in drei Y-Höhen gegen alle Tore.
* @return true wenn ein Tor gewertet wurde (Schleife soll abbrechen)
*/
private boolean checkPoint(double x, double y, double z, World world) {
for (double yOff : new double[]{0.0, 0.45, 0.9}) {
Location check = new Location(world, x, y + yOff, z);
for (SoccerGoal goal : goals.values()) {
if (goal.contains(check)) {
scoreGoal(goal);
return true;
}
}
}
return false;
}
// =========================================================================
// TOR WERTEN
// =========================================================================
private void scoreGoal(SoccerGoal goal) {
if (System.currentTimeMillis() - lastGoalTime < GOAL_COOLDOWN_MS) return;
lastGoalTime = System.currentTimeMillis();
scores.merge(goal.team, 1, Integer::sum);
String teamColor = goal.team == 1 ? "§9" : "§c";
String teamName = goal.team == 1 ? "Team Blau" : "Team Rot";
if (ball != null && ball.isValid()) ball.setVelocity(new Vector(0, 0, 0));
prevPos = null; // Reset damit kein Doppel-Trigger aus altem Segment
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendTitle(teamColor + "§lTOR!", teamColor + teamName + " §8| §fStand: " + getScoreString(), 5, 50, 15);
p.sendMessage("§8[§6Soccer§8] " + teamColor + "§lTOR! §r§7für " + teamName + " §8| §fStand: " + getScoreString());
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f);
}
spawnGoalCelebration(goal);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::respawnBall, GOAL_RESPAWN_DELAY);
}
private void spawnGoalCelebration(SoccerGoal goal) {
Location center = goal.getCenter();
World world = center.getWorld();
if (world == null) return;
for (int wave = 0; wave < 6; wave++) {
final double offsetY = wave * 0.25;
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
Location wLoc = center.clone().add(0, offsetY, 0);
world.spawnParticle(Particle.DUST, wLoc, 50, 1.2, 0.6, 1.2, 0, DUST_GOAL_FLASH);
world.spawnParticle(Particle.FIREWORK, wLoc, 25, 1.0, 0.8, 1.0, 0.25);
world.spawnParticle(Particle.EXPLOSION, center, 2, 0.4, 0.4, 0.4, 0.05);
}, wave * 7L);
}
}
// =========================================================================
// PHYSIK
// =========================================================================
private void handleDribbling() {
if (ball == null || !ball.isValid()) return;
if (System.currentTimeMillis() - lastKickTime < KICK_DRIBBLE_LOCK_MS) return;
for (Entity nearby : ball.getNearbyEntities(DRIBBLE_DETECTION_RADIUS, DRIBBLE_HEIGHT, DRIBBLE_DETECTION_RADIUS)) {
if (nearby instanceof Player player) {
Vector dir = ball.getLocation().toVector().subtract(player.getLocation().toVector());
if (dir.lengthSquared() > 0) {
dir.normalize().setY(DRIBBLE_LIFT);
ball.setVelocity(dir.multiply(DRIBBLE_FORCE));
lastMoveTime = System.currentTimeMillis();
}
}
}
}
private void handleWallBounce(Vector vel) {
// Nur prüfen wenn genug horizontale Bewegung vorhanden ist.
// Y-Komponente absichtlich ignoriert – der Bounce gilt nur für Wände (X/Z),
// nicht für Boden/Decke. So verhindert man dass liegende Boden-Blöcke
// fälschlicherweise als Wand erkannt werden und den Ball dauerhaft blockieren.
double hSpeed = vel.getX() * vel.getX() + vel.getZ() * vel.getZ();
if (hSpeed < 0.001) return;
Location loc = ball.getLocation();
// Y + 0.5 anheben damit wir auf Wand-Höhe prüfen, nicht auf Boden-Ebene.
// Ohne den Offset würde loc.getY() (Boden des ArmorStands) den Bodenblock selbst treffen.
Block nx = loc.clone().add(vel.getX() * WALL_CHECK_DISTANCE, 0.5, 0).getBlock();
Block nz = loc.clone().add(0, 0.5, vel.getZ() * WALL_CHECK_DISTANCE).getBlock();
boolean bounced = false;
if (nx.getType().isSolid()) { vel.setX(-vel.getX() * WALL_BOUNCE_DAMPING); bounced = true; }
if (nz.getType().isSolid()) { vel.setZ(-vel.getZ() * WALL_BOUNCE_DAMPING); bounced = true; }
if (bounced) {
ball.setVelocity(vel);
ball.getWorld().playSound(loc, 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 w = loc.getWorld();
if (w == null) return;
if (speed > PARTICLE_SPEED_HIGH) w.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0);
else if (speed > PARTICLE_SPEED_MEDIUM) w.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08);
else w.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
}
// =========================================================================
// TOR-PARTIKEL ZEICHNEN
// =========================================================================
private void drawAllGoalParticles() {
for (SoccerGoal goal : goals.values()) drawGoalFrame(goal);
}
private void drawGoalFrame(SoccerGoal goal) {
World world = goal.getWorld();
if (world == null) return;
Particle.DustOptions dust = goal.team == 1 ? DUST_TEAM1 : DUST_TEAM2;
double x1 = Math.min(goal.pos1.getX(), goal.pos2.getX()), x2 = Math.max(goal.pos1.getX(), goal.pos2.getX());
double y1 = Math.min(goal.pos1.getY(), goal.pos2.getY()), y2 = Math.max(goal.pos1.getY(), goal.pos2.getY());
double z1 = Math.min(goal.pos1.getZ(), goal.pos2.getZ()), z2 = Math.max(goal.pos1.getZ(), goal.pos2.getZ());
line(world, x1,y1,z1, x2,y1,z1, dust); line(world, x1,y1,z2, x2,y1,z2, dust);
line(world, x1,y1,z1, x1,y1,z2, dust); line(world, x2,y1,z1, x2,y1,z2, dust);
line(world, x1,y2,z1, x2,y2,z1, dust); line(world, x1,y2,z2, x2,y2,z2, dust);
line(world, x1,y2,z1, x1,y2,z2, dust); line(world, x2,y2,z1, x2,y2,z2, dust);
line(world, x1,y1,z1, x1,y2,z1, dust); line(world, x2,y1,z1, x2,y2,z1, dust);
line(world, x1,y1,z2, x1,y2,z2, dust); line(world, x2,y1,z2, x2,y2,z2, dust);
}
private void line(World world, double ax, double ay, double az,
double bx, double by, double bz, Particle.DustOptions dust) {
double dx = bx-ax, dy = by-ay, dz = bz-az;
double len = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (len < 0.01) return;
int steps = (int) Math.ceil(len / PARTICLE_STEP);
dx /= steps; dy /= steps; dz /= steps;
for (int i = 0; i <= steps; i++)
world.spawnParticle(Particle.DUST, ax+dx*i, ay+dy*i, az+dz*i, 1, 0,0,0,0, dust, true);
}
// =========================================================================
// DATEN-KLASSE: SoccerGoal
// =========================================================================
private static class SoccerGoal {
final String name; final Location pos1, pos2; final int team;
SoccerGoal(String name, Location pos1, Location pos2, int team) {
this.name = name; this.pos1 = pos1; this.pos2 = pos2; this.team = team;
}
boolean contains(Location loc) {
if (loc.getWorld() == null || !loc.getWorld().equals(pos1.getWorld())) return false;
return loc.getX() >= Math.min(pos1.getX(), pos2.getX())
&& loc.getX() <= Math.max(pos1.getX(), pos2.getX())
&& loc.getY() >= Math.min(pos1.getY(), pos2.getY())
&& loc.getY() <= Math.max(pos1.getY(), pos2.getY())
&& loc.getZ() >= Math.min(pos1.getZ(), pos2.getZ())
&& loc.getZ() <= Math.max(pos1.getZ(), pos2.getZ());
}
Location getCenter() {
return new Location(pos1.getWorld(),
(pos1.getX()+pos2.getX())/2.0,
(pos1.getY()+pos2.getY())/2.0,
(pos1.getZ()+pos2.getZ())/2.0);
}
World getWorld() { return pos1.getWorld(); }
}
// =========================================================================
// CONFIG: TORE
// =========================================================================
private void loadGoals() {
goals.clear();
FileConfiguration config = NexusLobby.getInstance().getConfig();
ConfigurationSection section = config.getConfigurationSection("ball.goals");
if (section == null) return;
for (String name : section.getKeys(false)) {
String path = "ball.goals." + name;
Location pos1 = loadLoc(config, path + ".pos1");
Location pos2 = loadLoc(config, path + ".pos2");
if (pos1 == null || pos2 == null) continue;
goals.put(name, new SoccerGoal(name, pos1, pos2, config.getInt(path + ".team", 1)));
}
Bukkit.getLogger().info("[Soccer] " + goals.size() + " Tor(e) geladen.");
}
private void saveGoal(SoccerGoal g) {
String path = "ball.goals." + g.name;
FileConfiguration c = NexusLobby.getInstance().getConfig();
saveLoc(c, path + ".pos1", g.pos1); saveLoc(c, path + ".pos2", g.pos2);
c.set(path + ".team", g.team);
NexusLobby.getInstance().saveConfig();
}
private void deleteGoal(String name) {
NexusLobby.getInstance().getConfig().set("ball.goals." + name, null);
NexusLobby.getInstance().saveConfig();
}
private static void saveLoc(FileConfiguration c, String path, Location loc) {
if (loc == null || loc.getWorld() == null) return;
c.set(path + ".world", loc.getWorld().getName());
c.set(path + ".x", loc.getX()); c.set(path + ".y", loc.getY()); c.set(path + ".z", loc.getZ());
}
private static Location loadLoc(FileConfiguration c, String path) {
String wName = c.getString(path + ".world");
if (wName == null) return null;
World world = Bukkit.getWorld(wName);
if (world == null) { Bukkit.getLogger().warning("[Soccer] Welt nicht gefunden: " + wName); return null; }
return new Location(world, c.getDouble(path + ".x"), c.getDouble(path + ".y"), c.getDouble(path + ".z"));
}
// =========================================================================
// BALL SPAWNEN / RESPAWNEN
// =========================================================================
private void spawnBall() {
if (spawnLocation == null || spawnLocation.getWorld() == null) return;
if (ball != null && ball.isValid()) return;
ball = (ArmorStand) spawnLocation.getWorld().spawnEntity(spawnLocation.clone(), 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(true); ball.addScoreboardTag(BALL_TAG);
if (ball.getEquipment() != null) ball.getEquipment().setHelmet(getSoccerHead());
prevPos = spawnLocation.clone();
lastMoveTime = System.currentTimeMillis();
}
public void respawnBall() {
if (ball != null && ball.isValid()) { ball.remove(); ball = null; }
removeAllOldBalls();
prevPos = null;
spawnBall();
}
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;
}
// =========================================================================
// CLEANUP
// =========================================================================
private void removeDuplicateBalls() {
if (ball == null || !ball.isValid() || ball.getLocation().getWorld() == null) return;
for (Entity e : ball.getLocation().getWorld().getNearbyEntities(ball.getLocation(), 50, 50, 50)) {
if (e instanceof ArmorStand s && isBallEntity(s) && !s.getUniqueId().equals(ball.getUniqueId()))
s.remove();
}
}
private void removeAllOldBalls() {
int removed = 0;
if (spawnLocation != null && spawnLocation.getWorld() != null)
removed = cleanupWorld(spawnLocation.getWorld());
else
for (World w : Bukkit.getWorlds()) removed += cleanupWorld(w);
if (removed > 0) Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt.");
}
private int cleanupWorld(World world) {
int n = 0;
for (Entity e : world.getEntities()) {
if (e instanceof ArmorStand s && isBallEntity(s)
&& (ball == null || !s.getUniqueId().equals(ball.getUniqueId()))) {
s.remove(); n++;
}
}
return n;
}
private boolean isBallEntity(ArmorStand s) {
if (s.getScoreboardTags().contains(BALL_TAG)) return true;
if (BALL_NAME.equals(s.getCustomName())) return true;
if (s.getEquipment() != null && s.getEquipment().getHelmet() != null) {
ItemStack h = s.getEquipment().getHelmet();
if (h.getType() == Material.PLAYER_HEAD && h.hasItemMeta()) {
SkullMeta m = (SkullMeta) h.getItemMeta();
if (m.hasOwner() && m.getOwnerProfile() != null
&& BALL_NAME.equals(m.getOwnerProfile().getName())) return true;
}
}
if (spawnLocation != null && spawnLocation.getWorld() != null
&& s.getWorld().equals(spawnLocation.getWorld())
&& s.isSmall() && s.isInvisible() && !s.hasBasePlate()
&& s.getLocation().distance(spawnLocation) < CLEANUP_RADIUS
&& s.getEquipment() != null && s.getEquipment().getHelmet() != null
&& s.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD)
return true;
return false;
}
// =========================================================================
// EVENTS
// =========================================================================
@EventHandler
public void onBallPunch(EntityDamageByEntityEvent event) {
if (ball == null || !event.getEntity().equals(ball)) return;
event.setCancelled(true);
if (!(event.getDamager() instanceof Player p)) return;
Vector dir = p.getLocation().getDirection().normalize();
dir.multiply(KICK_FORCE).setY(KICK_LIFT);
// prevPos auf aktuelle Position setzen bevor der Ball sich bewegt –
// so startet das nächste Segment am richtigen Punkt.
prevPos = ball.getLocation().clone();
lastKickTime = System.currentTimeMillis();
ball.setVelocity(dir);
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);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
// Wenn ein Spieler joinт: Ball-State prüfen und ggf. resynchronisieren.
// Passiert z.B. nach Server-Wechsel via BungeeCord: der Chunk wird neu geladen,
// alte Ball-Entities tauchen wieder auf, die interne Referenz zeigt evtl. auf
// die falsche oder eine ungültige Instanz.
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
if (spawnLocation == null || spawnLocation.getWorld() == null) return;
// Alle alten nexusball-Entities in der Welt suchen
boolean foundValid = false;
for (Entity e : spawnLocation.getWorld().getEntities()) {
if (!(e instanceof ArmorStand as)) continue;
if (!isBallEntity(as)) continue;
if (ball != null && ball.isValid() && as.getUniqueId().equals(ball.getUniqueId())) {
// Referenz ist noch korrekt und gültig – alles gut
foundValid = true;
} else if (ball == null || !ball.isValid()) {
// Unsere Referenz ist verloren – die gefundene Entity übernehmen
ball = as;
prevPos = as.getLocation().clone();
lastMoveTime = System.currentTimeMillis();
foundValid = true;
Bukkit.getLogger().info("[Soccer] Ball-Referenz nach Join wiederhergestellt.");
} else {
// Duplikat – entfernen
as.remove();
}
}
if (!foundValid) {
// Kein Ball in der Welt – neu spawnen
respawnBall();
Bukkit.getLogger().info("[Soccer] Ball nach Join neu gespawnt.");
}
}, 20L); // 1s warten damit der Chunk sicher geladen ist
}
// =========================================================================
// COMMANDS
// =========================================================================
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player p)) { sender.sendMessage("§cNur Spieler."); return true; }
if (!p.hasPermission("nexuslobby.admin")) { p.sendMessage("§cKeine Berechtigung!"); return true; }
if (args.length < 2 || !args[0].equalsIgnoreCase("ball")) return false;
switch (args[1].toLowerCase()) {
case "setspawn" -> cmdSetSpawn(p);
case "respawn" -> { respawnBall(); p.sendMessage("§8[§6Soccer§8] §eBall neu gespawnt."); }
case "remove" -> cmdRemove(p);
case "goal" -> cmdGoal(p, args);
case "score" -> cmdScore(p, args);
default -> sendHelp(p);
}
return true;
}
private void cmdSetSpawn(Player p) {
spawnLocation = p.getLocation();
FileConfiguration c = NexusLobby.getInstance().getConfig();
c.set("ball.spawn.world", spawnLocation.getWorld().getName());
c.set("ball.spawn.x", spawnLocation.getX());
c.set("ball.spawn.y", spawnLocation.getY());
c.set("ball.spawn.z", spawnLocation.getZ());
NexusLobby.getInstance().saveConfig();
respawnBall();
p.sendMessage("§8[§6Soccer§8] §aBall-Spawn gesetzt und Ball respawnt!");
}
private void cmdRemove(Player p) {
if (ball != null) { ball.remove(); ball = null; }
removeAllOldBalls();
p.sendMessage("§8[§6Soccer§8] §cBall entfernt.");
}
private void cmdGoal(Player p, String[] args) {
if (args.length < 3) { sendGoalHelp(p); return; }
switch (args[2].toLowerCase()) {
case "pos1" -> {
selPos1.put(p.getUniqueId(), p.getLocation().clone());
p.sendMessage("§8[§6Soccer§8] §aPos1 §7gesetzt bei §e" + fmt(p.getLocation()));
p.sendMessage("§8[§6Soccer§8] §7Zur gegenüberliegenden Ecke und §e/nexuslobby ball goal pos2§7 eingeben.");
}
case "pos2" -> {
selPos2.put(p.getUniqueId(), p.getLocation().clone());
p.sendMessage("§8[§6Soccer§8] §aPos2 §7gesetzt bei §e" + fmt(p.getLocation()));
p.sendMessage("§8[§6Soccer§8] §7Speichern mit §e/nexuslobby ball goal save <n> <1|2>§7.");
}
case "save" -> {
if (args.length < 5) { p.sendMessage("§cVerwendung: /nexuslobby ball goal save <n> <1|2>"); return; }
Location p1 = selPos1.get(p.getUniqueId());
Location p2 = selPos2.get(p.getUniqueId());
if (p1 == null || p2 == null) { p.sendMessage("§cZuerst pos1 und pos2 setzen!"); return; }
int team;
try { team = Integer.parseInt(args[4]); }
catch (NumberFormatException e) { p.sendMessage("§cTeam muss 1 oder 2 sein!"); return; }
if (team != 1 && team != 2) { p.sendMessage("§cTeam muss 1 §9(Blau)§c oder 2 §c(Rot) sein!"); return; }
String name = args[3];
SoccerGoal goal = new SoccerGoal(name, p1, p2, team);
goals.put(name, goal);
saveGoal(goal);
selPos1.remove(p.getUniqueId()); selPos2.remove(p.getUniqueId());
p.sendMessage("§8[§6Soccer§8] §aTor §e" + name + " §afür " + (team==1?"§9":"§c") + "Team " + team + " §aerstellt!");
p.sendMessage("§8[§6Soccer§8] §7Nutze §e/nexuslobby ball goal show §7um die Box zu sehen.");
drawGoalFrame(goal);
}
case "delete" -> {
if (args.length < 4) { p.sendMessage("§cVerwendung: /nexuslobby ball goal delete <n>"); return; }
String name = args[3];
if (goals.remove(name) != null) {
deleteGoal(name);
p.sendMessage("§8[§6Soccer§8] §cTor §e" + name + " §cgelöscht.");
} else {
p.sendMessage("§cTor '" + name + "' nicht gefunden. Tore: " + String.join(", ", goals.keySet()));
}
}
case "list" -> {
if (goals.isEmpty()) { p.sendMessage("§8[§6Soccer§8] §7Keine Tore gesetzt."); return; }
p.sendMessage("§8[§6Soccer§8] §6Registrierte Tore §8(" + goals.size() + ")§6:");
goals.forEach((name, g) -> p.sendMessage(
"§8 » §e" + name + " §8— " + (g.team==1?"§9Blau":"§cRot") +
" §8| §7" + fmt(g.pos1) + " §8→ §7" + fmt(g.pos2)));
}
case "show" -> {
for (int i = 0; i < 10; i++) {
final int tick = i * 4;
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::drawAllGoalParticles, tick);
}
p.sendMessage("§8[§6Soccer§8] §aTore werden für ~2 Sekunden angezeigt.");
}
case "debug" -> {
if (ball == null || !ball.isValid()) { p.sendMessage("§8[§6Soccer§8] §cKein Ball."); return; }
Location bl = ball.getLocation();
Vector bv = ball.getVelocity();
p.sendMessage("§8[§6Soccer§8] §6Ball getLocation(): §f" + fmtFull(bl));
p.sendMessage("§8[§6Soccer§8] §6Ball getVelocity(): §f" + fmtVec(bv) + " §7(speed=" + String.format("%.4f", bv.length()) + ")");
p.sendMessage("§8[§6Soccer§8] §6prevPos: §f" + (prevPos != null ? fmtFull(prevPos) : "null"));
if (!goals.isEmpty()) {
p.sendMessage("§8[§6Soccer§8] §7Direkter Punkt-Check:");
for (SoccerGoal g : goals.values()) {
boolean h0 = g.contains(bl.clone());
boolean h1 = g.contains(bl.clone().add(0, 0.45, 0));
boolean h2 = g.contains(bl.clone().add(0, 0.9, 0));
p.sendMessage(" §e" + g.name + " §7Fuß=" + cb(h0) + " Mitte=" + cb(h1) + " Kopf=" + cb(h2));
}
}
}
default -> sendGoalHelp(p);
}
}
private void cmdScore(Player p, String[] args) {
if (args.length >= 3 && args[2].equalsIgnoreCase("reset")) {
scores.put(1, 0);
scores.put(2, 0);
for (Player pl : Bukkit.getOnlinePlayers()) {
pl.sendMessage("§8[§6Soccer§8] §eStand wurde zurückgesetzt! §8| §fNeuer Stand: " + getScoreString());
pl.sendTitle("§eStand zurückgesetzt!", "§90 §8: §c0", 5, 40, 10);
pl.playSound(pl.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.2f);
}
Bukkit.getLogger().info("[Soccer] Stand von " + p.getName() + " zurückgesetzt.");
} else {
p.sendMessage("§8[§6Soccer§8] §6Aktueller Stand: §f" + getScoreString());
p.sendMessage("§8[§6Soccer§8] §7Reset: §e/nexuslobby ball score reset");
}
}
// =========================================================================
// HILFSMETHODEN
// =========================================================================
private String getScoreString() {
return "§9" + scores.getOrDefault(1, 0) + " §8: §c" + scores.getOrDefault(2, 0);
}
private static String fmt(Location l) {
return String.format("%.0f/%.0f/%.0f", l.getX(), l.getY(), l.getZ());
}
private static String fmtFull(Location l) {
return String.format("%.3f/%.3f/%.3f", l.getX(), l.getY(), l.getZ());
}
private static String fmtVec(Vector v) {
return String.format("%.3f/%.3f/%.3f", v.getX(), v.getY(), v.getZ());
}
private static String cb(boolean b) { return b ? "§atrue §r" : "§cfalse §r"; }
private void loadConfigLocation() {
FileConfiguration c = NexusLobby.getInstance().getConfig();
if (!c.contains("ball.spawn.world")) return;
String wName = c.getString("ball.spawn.world");
World world = Bukkit.getWorld(wName);
if (world == null) {
Bukkit.getLogger().warning("[Soccer] Spawn-Welt '" + wName + "' nicht gefunden!");
return;
}
spawnLocation = new Location(world,
c.getDouble("ball.spawn.x"), c.getDouble("ball.spawn.y"), c.getDouble("ball.spawn.z"));
}
private void sendHelp(Player p) {
p.sendMessage("§8§m─────────────§r §6§lSoccer §r§8§m─────────────");
p.sendMessage("§e/nexuslobby ball setspawn §7Ball-Spawn setzen");
p.sendMessage("§e/nexuslobby ball respawn §7Ball neu spawnen");
p.sendMessage("§e/nexuslobby ball remove §7Ball entfernen");
p.sendMessage("§e/nexuslobby ball goal ... §7Tore verwalten");
p.sendMessage("§e/nexuslobby ball score §7Stand anzeigen");
p.sendMessage("§e/nexuslobby ball score reset §7Stand zurücksetzen");
p.sendMessage("§8§m─────────────────────────────────────────");
}
private void sendGoalHelp(Player p) {
p.sendMessage("§8§m─────────────§r §6§lTore einrichten §r§8§m─────────────");
p.sendMessage("§71. §7Stehe in eine Ecke des Tors:");
p.sendMessage(" §e/nexuslobby ball goal pos1");
p.sendMessage("§72. §7Zur gegenüberliegenden Ecke gehen:");
p.sendMessage(" §e/nexuslobby ball goal pos2");
p.sendMessage("§73. §7Speichern §8(Team 1=§9Blau§8, 2=§cRot§8)§7:");
p.sendMessage(" §e/nexuslobby ball goal save <n> <1|2>");
p.sendMessage("§e/nexuslobby ball goal delete <n> §7Tor löschen");
p.sendMessage("§e/nexuslobby ball goal list §7Alle Tore");
p.sendMessage("§e/nexuslobby ball goal show §7Tore anzeigen");
p.sendMessage("§e/nexuslobby ball goal debug §7Ball-Position debuggen");
p.sendMessage("§8§m─────────────────────────────────────────────────");
}
}
@@ -0,0 +1,108 @@
package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.portal.PortalCommand;
import de.nexuslobby.modules.border.BorderModule;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class BorderCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.admin")) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKeine Rechte.");
return true;
}
if (args.length == 0) {
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lNexus WorldBorder Setup");
p.sendMessage("§e/border circle <Radius> §7- Kreis um dich herum");
p.sendMessage("§e/border square §7- Nutzt Axt-Markierung (Viereck)");
p.sendMessage("§e/border disable §7- Grenze deaktivieren");
p.sendMessage("§8§m------------------------------------");
return true;
}
var config = NexusLobby.getInstance().getConfig();
switch (args[0].toLowerCase()) {
case "circle" -> {
if (args.length < 2) {
p.sendMessage("§cBitte gib einen Radius an.");
return true;
}
try {
double radius = Double.parseDouble(args[1]);
Location loc = p.getLocation();
config.set("worldborder.type", "CIRCLE");
config.set("worldborder.enabled", true);
config.set("worldborder.radius", radius);
// FIX: Location als einzelne Werte speichern statt als Objekt.
// config.set(key, Location) ist nach Neustarts unzuverlässig,
// weil config.getLocation() die Welt zum Deserialisierungs-
// zeitpunkt auflösen muss und das fehlschlagen kann.
config.set("worldborder.center.world", loc.getWorld().getName());
config.set("worldborder.center.x", loc.getX());
config.set("worldborder.center.y", loc.getY());
config.set("worldborder.center.z", loc.getZ());
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aKreis-Grenze (Radius: " + radius + ") gesetzt.");
} catch (NumberFormatException e) {
p.sendMessage("§cUngültige Zahl.");
return true;
}
}
case "square" -> {
Location l1 = PortalCommand.getSelection1(p);
Location l2 = PortalCommand.getSelection2(p);
if (l1 == null || l2 == null) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cBitte markiere erst 2 Punkte mit der Portalwand!");
return true;
}
config.set("worldborder.type", "SQUARE");
config.set("worldborder.enabled", true);
// FIX: Beide Positionen als einzelne Werte speichern
config.set("worldborder.pos1.world", l1.getWorld().getName());
config.set("worldborder.pos1.x", l1.getX());
config.set("worldborder.pos1.y", l1.getY());
config.set("worldborder.pos1.z", l1.getZ());
config.set("worldborder.pos2.world", l2.getWorld().getName());
config.set("worldborder.pos2.x", l2.getX());
config.set("worldborder.pos2.y", l2.getY());
config.set("worldborder.pos2.z", l2.getZ());
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aViereckige Grenze erfolgreich gesetzt.");
}
case "disable" -> {
config.set("worldborder.enabled", false);
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cGrenze wurde deaktiviert.");
}
default -> {
p.sendMessage("§cUnbekannter Unterbefehl.");
return true;
}
}
NexusLobby.getInstance().saveConfig();
// Update das Modul direkt im RAM
BorderModule module = NexusLobby.getInstance().getModuleManager().getModule(BorderModule.class);
if (module != null) {
module.reloadConfig();
}
return true;
}
}
@@ -0,0 +1,162 @@
package de.nexuslobby.modules.border;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
/**
* Verwaltet die Lobby-Begrenzung (Kreis oder Rechteck).
* Speichert Daten in der Haupt-config.yml unter 'worldborder'.
*
* FIX: Locations werden als einzelne Werte (worldName, x, y, z) gespeichert
* statt als serialisiertes Location-Objekt, da config.getLocation()
* nach einem Neustart unzuverlässig ist.
*/
public class BorderModule implements Module, Listener {
private String type;
// FIX: Statt Location-Objekte die rohen Werte cachen
private String pos1World, pos2World, centerWorld;
private double pos1X, pos1Y, pos1Z;
private double pos2X, pos2Y, pos2Z;
private double centerX, centerZ;
private double radius;
private boolean enabled;
private boolean hasPos1, hasPos2, hasCenter;
@Override
public String getName() { return "WorldBorder"; }
@Override
public void onEnable() {
reloadConfig();
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
}
@Override
public void onDisable() {
// FIX: Listener beim Deaktivieren abmelden, damit nach /reload
// keine doppelten Events gefeuert werden.
HandlerList.unregisterAll(this);
}
/**
* Lädt die Border-Einstellungen aus der config.yml.
* Wird auch von BorderCommand und NexusLobby.reloadPlugin() aufgerufen.
*
* FIX: Robuste Methode - liest world-Name und einzelne Koordinaten,
* kein config.getLocation() mehr (das nach Neustarts versagen kann).
*/
public void reloadConfig() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
this.enabled = config.getBoolean("worldborder.enabled", false);
this.type = config.getString("worldborder.type", "CIRCLE").toUpperCase();
this.radius = config.getDouble("worldborder.radius", 50.0);
// --- CIRCLE-Daten laden ---
this.hasCenter = config.contains("worldborder.center.world");
if (hasCenter) {
this.centerWorld = config.getString("worldborder.center.world");
this.centerX = config.getDouble("worldborder.center.x");
this.centerZ = config.getDouble("worldborder.center.z");
}
// --- SQUARE-Daten laden ---
this.hasPos1 = config.contains("worldborder.pos1.world");
this.hasPos2 = config.contains("worldborder.pos2.world");
if (hasPos1) {
this.pos1World = config.getString("worldborder.pos1.world");
this.pos1X = config.getDouble("worldborder.pos1.x");
this.pos1Y = config.getDouble("worldborder.pos1.y");
this.pos1Z = config.getDouble("worldborder.pos1.z");
}
if (hasPos2) {
this.pos2World = config.getString("worldborder.pos2.world");
this.pos2X = config.getDouble("worldborder.pos2.x");
this.pos2Y = config.getDouble("worldborder.pos2.y");
this.pos2Z = config.getDouble("worldborder.pos2.z");
}
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
if (!enabled || event.getTo() == null) return;
// Performance: Nur prüfen wenn sich Block-Koordinaten ändern
if (event.getFrom().getBlockX() == event.getTo().getBlockX() &&
event.getFrom().getBlockZ() == event.getTo().getBlockZ() &&
event.getFrom().getBlockY() == event.getTo().getBlockY()) return;
Player player = event.getPlayer();
// FIX: nexuslobby.border.bypass statt nur nexuslobby.admin prüfen
if (player.hasPermission("nexuslobby.border.bypass") ||
player.hasPermission("nexuslobby.admin") ||
player.getGameMode().name().equals("SPECTATOR")) return;
Location to = event.getTo();
boolean outside = false;
// --- Prüfung: Kreis-Border ---
if (type.equals("CIRCLE") && hasCenter) {
World cWorld = Bukkit.getWorld(centerWorld);
if (cWorld != null && to.getWorld().equals(cWorld)) {
double distSq = Math.pow(to.getX() - centerX, 2) + Math.pow(to.getZ() - centerZ, 2);
if (distSq > radius * radius) outside = true;
}
}
// --- Prüfung: Rechteck-Border (Square) ---
else if (type.equals("SQUARE") && hasPos1 && hasPos2) {
World bWorld = Bukkit.getWorld(pos1World);
if (bWorld != null && to.getWorld().equals(bWorld)) {
double minX = Math.min(pos1X, pos2X);
double maxX = Math.max(pos1X, pos2X);
double minZ = Math.min(pos1Z, pos2Z);
double maxZ = Math.max(pos1Z, pos2Z);
if (to.getX() < minX || to.getX() > maxX ||
to.getZ() < minZ || to.getZ() > maxZ) {
outside = true;
}
}
}
if (outside) {
Location spawnLocation = getMainSpawnLocation();
if (spawnLocation != null) {
player.teleport(spawnLocation);
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cDu hast den Lobby-Bereich verlassen!");
}
}
}
/**
* Holt den Spawnpunkt aus der Config (Pfad: spawn.world, spawn.x, etc.)
*/
private Location getMainSpawnLocation() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
String worldName = config.getString("spawn.world");
if (worldName != null) {
World w = Bukkit.getWorld(worldName);
if (w != null) {
return new Location(w,
config.getDouble("spawn.x"),
config.getDouble("spawn.y"),
config.getDouble("spawn.z"),
(float) config.getDouble("spawn.yaw"),
(float) config.getDouble("spawn.pitch"));
}
}
return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0).getSpawnLocation();
}
}
@@ -0,0 +1,70 @@
package de.nexuslobby.modules.gadgets;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
public class Balloon {
private final UUID playerUUID;
private final LivingEntity balloonEntity;
private final ArmorStand headStand;
public Balloon(Player player, Material balloonMaterial) {
this.playerUUID = player.getUniqueId();
Location loc = player.getLocation().add((double)0.0F, (double)2.0F, (double)0.0F);
this.balloonEntity = (LivingEntity)loc.getWorld().spawnEntity(loc, EntityType.PIG);
this.balloonEntity.setInvisible(true);
this.balloonEntity.setSilent(true);
this.balloonEntity.setInvulnerable(true);
this.balloonEntity.setGravity(false);
this.balloonEntity.setLeashHolder(player);
this.balloonEntity.addScoreboardTag("nexus_balloon");
this.headStand = (ArmorStand)loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND);
this.headStand.setVisible(false);
this.headStand.setGravity(false);
this.headStand.setMarker(true);
this.headStand.setHelmet(new ItemStack(balloonMaterial));
}
public void update() {
Player player = Bukkit.getPlayer(this.playerUUID);
if (player != null && player.isOnline() && this.balloonEntity != null && this.balloonEntity.isValid()) {
if (!this.balloonEntity.isLeashed()) {
this.remove();
} else {
Location targetLoc = player.getLocation().clone().add((double)0.0F, (double)2.5F, (double)0.0F);
Vector direction = targetLoc.toVector().subtract(this.balloonEntity.getLocation().toVector());
double distance = direction.length();
if (distance > (double)5.0F) {
this.balloonEntity.teleport(targetLoc);
} else if (distance > 0.1) {
this.balloonEntity.setVelocity(direction.multiply(0.4));
}
this.headStand.teleport(this.balloonEntity.getLocation().clone().subtract((double)0.0F, (double)1.5F, (double)0.0F));
}
} else {
this.remove();
}
}
public void remove() {
if (this.balloonEntity != null) {
this.balloonEntity.setLeashHolder((Entity)null);
this.balloonEntity.remove();
}
if (this.headStand != null) {
this.headStand.remove();
}
}
}
@@ -0,0 +1,54 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
public class ChickenRain {
private static final Set<UUID> activeRains = Collections.synchronizedSet(new HashSet());
public static void start(final Player player) {
if (!activeRains.contains(player.getUniqueId())) {
activeRains.add(player.getUniqueId());
(new BukkitRunnable() {
int ticks = 0;
final Random random = new Random();
public void run() {
if (player.isOnline() && this.ticks <= 100) {
if (this.ticks % 2 == 0) {
Location spawnLoc = player.getLocation().add((this.random.nextDouble() - (double)0.5F) * (double)4.0F, (double)4.0F, (this.random.nextDouble() - (double)0.5F) * (double)4.0F);
Chicken chicken = (Chicken)spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.CHICKEN);
chicken.setBaby();
chicken.setInvulnerable(true);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
if (chicken.isValid()) {
chicken.getWorld().spawnParticle(Particle.CLOUD, chicken.getLocation(), 5, 0.2, 0.2, 0.2, 0.1);
chicken.getWorld().playSound(chicken.getLocation(), Sound.ENTITY_CHICKEN_EGG, 1.0F, 1.5F);
chicken.remove();
}
}, 30L);
}
++this.ticks;
} else {
ChickenRain.activeRains.remove(player.getUniqueId());
this.cancel();
}
}
}).runTaskTimer(NexusLobby.getInstance(), 0L, 1L);
}
}
}
@@ -0,0 +1,100 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
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.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
public class FreezeRay {
private static final Set<UUID> frozenPlayers = new HashSet();
public static boolean isFrozen(UUID uuid) {
return frozenPlayers.contains(uuid);
}
public static void unfreeze(UUID uuid) {
frozenPlayers.remove(uuid);
}
public static void shoot(Player shooter) {
Location start = shooter.getEyeLocation();
Vector direction = start.getDirection();
shooter.getWorld().playSound(start, Sound.ENTITY_SNOW_GOLEM_SHOOT, 1.0F, 1.5F);
for(double d = (double)0.0F; d < (double)15.0F; d += 0.3) {
Location point = start.clone().add(direction.clone().multiply(d));
point.getWorld().spawnParticle(Particle.SNOWFLAKE, point, 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
for(Entity entity : point.getWorld().getNearbyEntities(point, 0.8, 0.8, 0.8)) {
if (entity instanceof Player target) {
if (target != shooter) {
if (GadgetShield.isProtected(target)) {
String reason = GadgetShield.isAdminShielded(target) ? "Dieser Spieler hat Gadget-Schutz aktiviert." : "Dieser Spieler ist gerade im Parkour.";
shooter.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§7" + reason);
return;
}
applyFreeze(target);
return;
}
}
}
if (point.getBlock().getType().isSolid()) {
break;
}
}
}
private static void applyFreeze(final Player target) {
if (!frozenPlayers.contains(target.getUniqueId())) {
frozenPlayers.add(target.getUniqueId());
final Location freezeLocation = target.getLocation();
target.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§bDu wurdest eingefroren!");
target.getWorld().playSound(target.getLocation(), Sound.BLOCK_GLASS_BREAK, 1.0F, 0.5F);
(new BukkitRunnable() {
int ticks = 0;
public void run() {
if (target.isOnline() && this.ticks < 60) {
if (GadgetShield.isProtected(target)) {
FreezeRay.frozenPlayers.remove(target.getUniqueId());
this.cancel();
} else {
target.setVelocity(new Vector(0, 0, 0));
Location current = target.getLocation();
if (current.getX() != freezeLocation.getX() || current.getZ() != freezeLocation.getZ()) {
freezeLocation.setYaw(current.getYaw());
freezeLocation.setPitch(current.getPitch());
target.teleport(freezeLocation);
}
Location loc = target.getLocation();
for(int i = 0; i < 8; ++i) {
double angle = (double)i * Math.PI / (double)4.0F;
double x = Math.cos(angle) * 0.7;
double z = Math.sin(angle) * 0.7;
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, (double)1.0F, z), 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 0.2, z), 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
}
this.ticks += 2;
}
} else {
FreezeRay.frozenPlayers.remove(target.getUniqueId());
this.cancel();
}
}
}).runTaskTimer(NexusLobby.getInstance(), 0L, 2L);
}
}
}
@@ -0,0 +1,778 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Slime;
import org.bukkit.entity.Zombie;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
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.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerUnleashEntityEvent;
import org.bukkit.event.player.PlayerFishEvent.State;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class GadgetModule implements Module, Listener {
private final Map<UUID, Integer> discoTasks = new HashMap();
private final Map<UUID, Balloon> activeBalloons = new HashMap();
private final Map<UUID, ParticleEffect> activeEffects = new HashMap();
private final Set<UUID> activeShields = new HashSet();
private final Map<UUID, Long> meteorCooldowns = new HashMap();
private final Map<UUID, Long> freezeCooldowns = new HashMap();
private final Map<UUID, Long> grapplingCooldowns = new HashMap();
private static final long METEOR_CD_MS = 15000L;
private static final long FREEZE_CD_MS = 10000L;
private static final long GRAPPLING_CD_MS = 3000L;
private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü";
private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons";
private final String PARTICLE_TITLE = "§b§lGadgets §8- §dPartikel";
private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges";
private final String HAT_TITLE = "§b§lGadgets §8- §aHüte & Köpfe";
private final String PET_TITLE = "§b§lGadgets §8- §dBegleiter";
private final String PET2_TITLE = "§b§lGadgets §8- §dBegleiter §7(2)";
private final String PET3_TITLE = "§b§lGadgets §8- §dBegleiter §7(3)";
public String getName() {
return "Gadgets";
}
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
PetManager.register();
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
PetManager.updatePets();
this.activeBalloons.values().forEach(Balloon::update);
for(Player p : Bukkit.getOnlinePlayers()) {
UUID uuid = p.getUniqueId();
this.handleSpecialHatEffects(p);
if (this.activeEffects.containsKey(uuid)) {
((ParticleEffect)this.activeEffects.get(uuid)).update(p);
}
if (this.activeShields.contains(uuid)) {
ShieldTask.handleShield(p);
}
}
}, 1L, 1L);
}
private boolean checkAndSetCooldown(Player player, Map<UUID, Long> cdMap, long durationMs, String gadgetName) {
long now = System.currentTimeMillis();
long last = (Long)cdMap.getOrDefault(player.getUniqueId(), 0L);
long remaining = durationMs - (now - last);
if (remaining > 0L) {
long secLeft = (long)Math.ceil((double)remaining / (double)1000.0F);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§c" + gadgetName + " §7hat noch §e" + secLeft + "s §7Cooldown.");
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5F, 0.8F);
return false;
} else {
cdMap.put(player.getUniqueId(), now);
return true;
}
}
@EventHandler
public void onInteractAtEntity(PlayerInteractAtEntityEvent event) {
if (event.getRightClicked().getScoreboardTags().contains("nexus_balloon")) {
event.setCancelled(true);
}
}
@EventHandler
public void onUnleash(PlayerUnleashEntityEvent event) {
if (event.getEntity().getScoreboardTags().contains("nexus_balloon")) {
event.setCancelled(true);
}
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
ItemStack item = event.getItem();
if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
String name = item.getItemMeta().getDisplayName();
if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
Player player = event.getPlayer();
if (!GadgetShield.isProtected(player) || !name.equals("§b§lFreeze-Ray") && !name.equals("§c§lMeteorit")) {
if (name.equals("§b§lFreeze-Ray")) {
event.setCancelled(true);
if (this.checkAndSetCooldown(player, this.freezeCooldowns, 10000L, "§b§lFreeze-Ray")) {
FreezeRay.shoot(player);
}
} else if (name.equals("§6§lPaintball-Gun")) {
PaintballGun.shoot(player);
event.setCancelled(true);
} else if (name.equals("§c§lMeteorit")) {
event.setCancelled(true);
if (this.checkAndSetCooldown(player, this.meteorCooldowns, 15000L, "§c§lMeteorit")) {
MeteorStrike.launch(player);
}
}
} else {
if (GadgetShield.isAdminShielded(player)) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cDein Gadget-Schutz ist aktiv. Deaktiviere ihn mit §e/nexus gadgetshield§c.");
} else {
player.sendMessage("§8[§6Parkour§8] §cGadgets können während des Parkours nicht benutzt werden.");
}
event.setCancelled(true);
}
}
}
}
@EventHandler
public void onFish(PlayerFishEvent event) {
Player player = event.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand();
if (item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
if (item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) {
if (event.getState() == State.IN_GROUND || event.getState() == State.REEL_IN || event.getState() == State.CAUGHT_ENTITY) {
if (event.getHook() == null) {
return;
}
if (GadgetShield.isProtected(player)) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cDer Enterhaken ist gerade nicht verfügbar.");
return;
}
if (!this.checkAndSetCooldown(player, this.grapplingCooldowns, 3000L, "§b§lEnterhaken")) {
return;
}
GrapplingHook.pullPlayer(player, event.getHook().getLocation());
}
}
}
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (FreezeRay.isFrozen(event.getPlayer().getUniqueId()) && (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) {
switch (hat.getType()) {
case CAMPFIRE:
p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add((double)0.0F, 2.2, (double)0.0F), 1, 0.05, 0.05, 0.05, 0.02);
break;
case SPAWNER:
p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add((double)0.0F, 2.1, (double)0.0F), 1, 0.12, 0.12, 0.12, 0.02);
break;
case SEA_LANTERN:
case BEACON:
p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add((double)0.0F, 2.1, (double)0.0F), 1, 0.1, 0.1, 0.1, 0.03);
break;
case ENCHANTING_TABLE:
p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add((double)0.0F, 2.3, (double)0.0F), 1, 0.2, 0.2, 0.2, (double)0.5F);
}
}
}
public void openGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 27, "§b§lGadgets §8- §7Menü");
this.fillEdges(gui);
gui.setItem(10, this.createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter"));
gui.setItem(11, this.createItem(Material.GOLDEN_HELMET, "§a§lHüte", "§7Setze dir etwas auf den Kopf"));
gui.setItem(13, this.createItem(Material.BONE, "§d§lBegleiter", "§7Echte Tiere, die dir folgen"));
gui.setItem(15, this.createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte"));
gui.setItem(16, this.createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Magische Auren & Effekte"));
gui.setItem(22, this.createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen"));
player.openInventory(gui);
}
private void openHatGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 45, "§b§lGadgets §8- §aHüte & Köpfe");
this.fillEdges(gui);
gui.setItem(10, this.createItem(Material.JACK_O_LANTERN, "§6Kürbis-Hut", "§7Es ist immer Halloween!"));
gui.setItem(11, this.createItem(Material.SEA_LANTERN, "§bMeeres-Leuchten", "§7§oEffekt: Glitzern"));
gui.setItem(12, this.createItem(Material.GLOWSTONE, "§eGlowstone-Kopf", "§7Werde zur Lampe"));
gui.setItem(13, this.createItem(Material.TNT, "§cExplosiv-Hut", "§7Vorsicht, heiß!"));
gui.setItem(14, this.createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?"));
gui.setItem(15, this.createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte"));
gui.setItem(16, this.createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!"));
gui.setItem(19, this.createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig..."));
gui.setItem(20, this.createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig"));
gui.setItem(21, this.createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land"));
gui.setItem(22, this.createItem(Material.SPAWNER, "§8Monster-Käfig", "§7§oEffekt: Flammen"));
gui.setItem(23, this.createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln"));
gui.setItem(24, this.createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf"));
gui.setItem(25, this.createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig"));
gui.setItem(28, this.createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast"));
gui.setItem(29, this.createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!"));
gui.setItem(30, this.createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern"));
gui.setItem(31, this.createItem(Material.CONDUIT, "§3Auge des Meeres", "§7Die Macht von Atlantis"));
gui.setItem(32, this.createItem(Material.ENCHANTING_TABLE, "§dMagier", "§7§oEffekt: Runen"));
gui.setItem(33, this.createItem(Material.CAMPFIRE, "§cHeißer Kopf", "§7§oEffekt: Rauch"));
gui.setItem(34, this.createItem(Material.SKELETON_SKULL, "§7Skelett", "§7Ein wenig gruselig"));
gui.setItem(36, this.createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
gui.setItem(40, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
player.openInventory(gui);
}
private void openPetGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 54, "§b§lGadgets §8- §dBegleiter");
this.fillEdges(gui);
gui.setItem(10, this.createItem(Material.BONE, "§fBaby-Wolf", "§7Ein kleiner treuer Begleiter"));
gui.setItem(11, this.createItem(Material.BONE, "§7Baby-Ashen Wolf", "§7Ein kleiner Aschen-Wolf"));
gui.setItem(12, this.createItem(Material.CAT_SPAWN_EGG, "§6Baby-Katze", "§7Ein kleiner Schmusefreund"));
gui.setItem(13, this.createItem(Material.PANDA_SPAWN_EGG, "§aBaby-Panda", "§7Ein kleiner Flauschball"));
gui.setItem(14, this.createItem(Material.FOX_SPAWN_EGG, "§6Baby-Fuchs", "§7Ein kleiner Schlaukopf"));
gui.setItem(15, this.createItem(Material.FROG_SPAWN_EGG, "§aBaby-Frosch", "§7Ein kleiner Hopsfreund"));
gui.setItem(16, this.createItem(Material.TURTLE_SPAWN_EGG, "§2Baby-Schildkröte", "§7Ein kleiner Panzenträger"));
gui.setItem(28, this.createItem(Material.FOX_SPAWN_EGG, "§bBaby-Schneefuchs", "§7Ein kleiner Schnee-Fuchs"));
gui.setItem(29, this.createItem(Material.RABBIT_SPAWN_EGG, "§fBaby-Kaninchen", "§7Ein kleiner Flitzer"));
gui.setItem(30, this.createItem(Material.AXOLOTL_SPAWN_EGG, "§dBaby-Axolotl", "§7Ein kleiner Wasserfreund"));
gui.setItem(31, this.createItem(Material.OCELOT_SPAWN_EGG, "§6Baby-Ozelot", "§7Ein kleiner Schleicher"));
gui.setItem(32, this.createItem(Material.POLAR_BEAR_SPAWN_EGG, "§fBaby-Eisbär", "§7Ein kleiner Eisbär"));
gui.setItem(33, this.createItem(Material.BEE_SPAWN_EGG, "§eBaby-Biene", "§7Eine kleine Biene"));
gui.setItem(34, this.createItem(Material.CHICKEN_SPAWN_EGG, "§fKüken", "§7Ein kleines Huhn"));
gui.setItem(19, this.createItem(Material.BONE, "§fWolf", "§7Ein treuer Begleiter"));
gui.setItem(20, this.createItem(Material.BONE, "§7Ashen Wolf", "§7Ein seltener Aschen-Wolf"));
gui.setItem(21, this.createItem(Material.CAT_SPAWN_EGG, "§6Katze", "§7Ein verschmuster Freund"));
gui.setItem(22, this.createItem(Material.PANDA_SPAWN_EGG, "§aPanda", "§7Ein gemütlicher Zeitgenosse"));
gui.setItem(23, this.createItem(Material.FOX_SPAWN_EGG, "§6Fuchs", "§7Ein schlauer Begleiter"));
gui.setItem(24, this.createItem(Material.FROG_SPAWN_EGG, "§aFrosch", "§7Ein echter Hopsmeister"));
gui.setItem(25, this.createItem(Material.TURTLE_SPAWN_EGG, "§2Schildkröte", "§7Gemütlich und gelassen"));
gui.setItem(37, this.createItem(Material.FOX_SPAWN_EGG, "§bSchneefuchs", "§7Ein Fuchs aus kalten Biomen"));
gui.setItem(38, this.createItem(Material.RABBIT_SPAWN_EGG, "§fKaninchen", "§7Ein schneller Hopper"));
gui.setItem(39, this.createItem(Material.AXOLOTL_SPAWN_EGG, "§dAxolotl", "§7Ein seltener Wasserfreund"));
gui.setItem(40, this.createItem(Material.OCELOT_SPAWN_EGG, "§6Ozelot", "§7Ein wachsamer Begleiter"));
gui.setItem(41, this.createItem(Material.POLAR_BEAR_SPAWN_EGG, "§fEisbär", "§7Ein starker Eisbär"));
gui.setItem(42, this.createItem(Material.BEE_SPAWN_EGG, "§eBiene", "§7Eine fleißige Biene"));
gui.setItem(43, this.createItem(Material.CHICKEN_SPAWN_EGG, "§fHuhn", "§7Ein gackerndes Huhn"));
gui.setItem(45, this.createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
gui.setItem(49, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
gui.setItem(53, this.createItem(Material.ARROW, "§7Weiter", "§8Seite 2"));
player.openInventory(gui);
}
private void openPetGUI2(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 54, "§b§lGadgets §8- §dBegleiter §7(2)");
this.fillEdges(gui);
gui.setItem(19, this.createItem(Material.ARMADILLO_SPAWN_EGG, "§7Baby-Armadillo", "§7Ein kleiner Gürteltier"));
gui.setItem(28, this.createItem(Material.ARMADILLO_SPAWN_EGG, "§7Armadillo", "§7Ein Gürteltier"));
gui.setItem(45, this.createItem(Material.ARROW, "§7Zurück", "§8Seite 1"));
gui.setItem(49, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
gui.setItem(53, this.createItem(Material.ARROW, "§7Weiter", "§8Seite 3"));
player.openInventory(gui);
}
private void openPetGUI3(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 54, "§b§lGadgets §8- §dBegleiter §7(3)");
this.fillEdges(gui);
gui.setItem(19, this.createItem(Material.PARROT_SPAWN_EGG, "§cPapagei", "§7Ein bunter Begleiter"));
gui.setItem(20, this.createItem(Material.ALLAY_SPAWN_EGG, "§bAllay", "§7Ein magischer Helfer"));
gui.setItem(21, this.createItem(Material.SNIFFER_SPAWN_EGG, "§aSniffer", "§7Ein uraltes Tier"));
gui.setItem(45, this.createItem(Material.ARROW, "§7Zurück", "§8Seite 2"));
gui.setItem(49, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
player.openInventory(gui);
}
private void openBalloonGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 45, "§b§lGadgets §8- §eBallons");
this.fillEdges(gui);
Material[] wools = new Material[]{Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL, Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, Material.BROWN_WOOL, Material.GREEN_WOOL, Material.RED_WOOL};
int slot = 10;
for(Material m : wools) {
if (slot == 17) {
slot = 19;
}
if (slot == 26) {
slot = 28;
}
gui.setItem(slot++, this.createItem(m, "§fBallon: " + m.name().replace("_WOOL", ""), "§7Klicke zum Ausrüsten"));
}
gui.setItem(36, this.createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
gui.setItem(40, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
player.openInventory(gui);
}
private void openParticleGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 36, "§b§lGadgets §8- §dPartikel");
this.fillEdges(gui);
gui.setItem(11, this.createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe in der Lobby"));
gui.setItem(13, this.createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Lass es brennen!"));
gui.setItem(15, this.createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Deine persönliche Abkühlung"));
gui.setItem(27, this.createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
gui.setItem(31, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
player.openInventory(gui);
}
private void openFunGUI(Player player) {
Inventory gui = Bukkit.createInventory((InventoryHolder)null, 36, "§b§lGadgets §8- §6Lustiges");
this.fillEdges(gui);
gui.setItem(10, this.createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft! §8(3s CD)"));
gui.setItem(11, this.createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Friere andere ein! §8(10s CD)"));
gui.setItem(12, this.createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Male die Lobby bunt aus!"));
gui.setItem(13, this.createItem(Material.FEATHER, "§f§lMini-Tornado", "§7Wirbele dich herum!"));
gui.setItem(14, this.createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Lass es krachen! §8(15s CD)"));
gui.setItem(15, this.createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz"));
gui.setItem(16, this.createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!"));
gui.setItem(19, this.createItem(Material.SLIME_BALL, "§a§lMob-Party", "§7Lustige Mini-Mobs tanzen um dich!"));
gui.setItem(20, this.createItem(Material.NOTE_BLOCK, "§d§lTanz-Emote", "§7Führe einen Tanz auf!"));
gui.setItem(21, this.createItem(Material.FIREWORK_ROCKET, "§c§lKonfetti-Kanone", "§7Schieße Konfetti in die Luft!"));
gui.setItem(22, this.createItem(Material.TNT, "§c§lFake-TNT", "§7Täusche andere mit einer harmlosen Explosion!"));
gui.setItem(27, this.createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
gui.setItem(31, this.createItem(Material.NETHER_STAR, "§7Hauptmenü", "§8Zum Gadget-Hauptmenü"));
player.openInventory(gui);
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
String title = event.getView().getTitle();
if (title.startsWith("§b§lGadgets")) {
event.setCancelled(true);
final Player player = (Player)event.getWhoClicked();
ItemStack item = event.getCurrentItem();
if (item != null && item.getType() != Material.AIR) {
if (item.getType() == Material.ARROW) {
String displayName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : "";
if (displayName.equals("§7Weiter")) {
if (title.equals("§b§lGadgets §8- §dBegleiter")) {
this.openPetGUI2(player);
return;
}
if (title.equals("§b§lGadgets §8- §dBegleiter §7(2)")) {
this.openPetGUI3(player);
return;
}
}
if (displayName.equals("§7Zurück")) {
if (title.equals("§b§lGadgets §8- §dBegleiter §7(2)")) {
this.openPetGUI(player);
return;
}
if (title.equals("§b§lGadgets §8- §dBegleiter §7(3)")) {
this.openPetGUI2(player);
return;
}
this.openGUI(player);
return;
}
}
if (item.getType() == Material.NETHER_STAR && title.startsWith("§b§lGadgets §8- §d")
|| item.getType() == Material.NETHER_STAR && title.equals("§b§lGadgets §8- §aHüte & Köpfe")
|| item.getType() == Material.NETHER_STAR && title.equals("§b§lGadgets §8- §eBallons")
|| item.getType() == Material.NETHER_STAR && title.equals("§b§lGadgets §8- §dPartikel")
|| item.getType() == Material.NETHER_STAR && title.equals("§b§lGadgets §8- §6Lustiges")) {
this.openGUI(player);
return;
}
if (title.equals("§b§lGadgets §8- §7Menü")) {
if (item.getType() == Material.LEAD) {
this.openBalloonGUI(player);
} else if (item.getType() == Material.GOLDEN_HELMET) {
this.openHatGUI(player);
} else if (item.getType() == Material.BONE) {
this.openPetGUI(player);
} else if (item.getType() == Material.NETHER_STAR) {
this.openParticleGUI(player);
} else if (item.getType() == Material.FIREWORK_ROCKET) {
this.openFunGUI(player);
} else if (item.getType() == Material.BARRIER) {
this.removeGadgets(player);
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §aHüte & Köpfe")) {
if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) {
String hatName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : item.getType().name();
HatManager.setHat(player, item.getType(), hatName);
player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1.0F, 1.0F);
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §dBegleiter")) {
String displayName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : "";
boolean spawned = true;
if (displayName.equals("§fBaby-Wolf")) {
PetManager.spawnEntityPet(player, "WOLF", true);
} else if (displayName.equals("§fWolf")) {
PetManager.spawnEntityPet(player, "WOLF", false);
} else if (displayName.equals("§7Baby-Ashen Wolf")) {
PetManager.spawnEntityPet(player, "WOLF", true, "ASHEN");
} else if (displayName.equals("§7Ashen Wolf")) {
PetManager.spawnEntityPet(player, "WOLF", false, "ASHEN");
} else if (displayName.equals("§6Baby-Katze")) {
PetManager.spawnEntityPet(player, "CAT", true);
} else if (displayName.equals("§6Katze")) {
PetManager.spawnEntityPet(player, "CAT", false);
} else if (displayName.equals("§aBaby-Panda")) {
PetManager.spawnEntityPet(player, "PANDA", true);
} else if (displayName.equals("§aPanda")) {
PetManager.spawnEntityPet(player, "PANDA", false);
} else if (displayName.equals("§6Baby-Fuchs")) {
PetManager.spawnEntityPet(player, "FOX", true);
} else if (displayName.equals("§6Fuchs")) {
PetManager.spawnEntityPet(player, "FOX", false);
} else if (displayName.equals("§bBaby-Schneefuchs")) {
PetManager.spawnEntityPet(player, "FOX", true, "SNOW");
} else if (displayName.equals("§bSchneefuchs")) {
PetManager.spawnEntityPet(player, "FOX", false, "SNOW");
} else if (displayName.equals("§6Baby-Ozelot")) {
PetManager.spawnEntityPet(player, "OCELOT", true);
} else if (displayName.equals("§6Ozelot")) {
PetManager.spawnEntityPet(player, "OCELOT", false);
} else if (displayName.equals("§aBaby-Frosch")) {
PetManager.spawnEntityPet(player, "FROG", true);
} else if (displayName.equals("§aFrosch")) {
PetManager.spawnEntityPet(player, "FROG", false);
} else if (displayName.equals("§2Baby-Schildkröte")) {
PetManager.spawnEntityPet(player, "TURTLE", true);
} else if (displayName.equals("§2Schildkröte")) {
PetManager.spawnEntityPet(player, "TURTLE", false);
} else if (displayName.equals("§fBaby-Kaninchen")) {
PetManager.spawnEntityPet(player, "RABBIT", true);
} else if (displayName.equals("§fKaninchen")) {
PetManager.spawnEntityPet(player, "RABBIT", false);
} else if (displayName.equals("§dBaby-Axolotl")) {
PetManager.spawnEntityPet(player, "AXOLOTL", true);
} else if (displayName.equals("§dAxolotl")) {
PetManager.spawnEntityPet(player, "AXOLOTL", false);
} else if (displayName.equals("§fBaby-Eisbär")) {
PetManager.spawnEntityPet(player, "POLAR_BEAR", true);
} else if (displayName.equals("§fEisbär")) {
PetManager.spawnEntityPet(player, "POLAR_BEAR", false);
} else if (displayName.equals("§eBaby-Biene")) {
PetManager.spawnEntityPet(player, "BEE", true);
} else if (displayName.equals("§eBiene")) {
PetManager.spawnEntityPet(player, "BEE", false);
} else if (displayName.equals("§fKüken")) {
PetManager.spawnEntityPet(player, "CHICKEN", true);
} else if (displayName.equals("§fHuhn")) {
PetManager.spawnEntityPet(player, "CHICKEN", false);
} else if (displayName.equals("§7Baby-Armadillo")) {
PetManager.spawnEntityPet(player, "ARMADILLO", true);
} else if (displayName.equals("§7Armadillo")) {
PetManager.spawnEntityPet(player, "ARMADILLO", false);
} else {
spawned = false;
}
if (spawned) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§dDein Pet wurde gerufen!");
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §dBegleiter §7(2)")) {
String displayName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : "";
boolean spawned = true;
if (displayName.equals("§7Baby-Armadillo")) {
PetManager.spawnEntityPet(player, "ARMADILLO", true);
} else if (displayName.equals("§7Armadillo")) {
PetManager.spawnEntityPet(player, "ARMADILLO", false);
} else {
spawned = false;
}
if (spawned) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§dDein Pet wurde gerufen!");
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §dBegleiter §7(3)")) {
String displayName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : "";
boolean spawned = true;
if (displayName.equals("§cPapagei")) {
PetManager.spawnEntityPet(player, "PARROT", false);
} else if (displayName.equals("§bAllay")) {
PetManager.spawnEntityPet(player, "ALLAY", false);
} else if (displayName.equals("§aSniffer")) {
PetManager.spawnEntityPet(player, "SNIFFER", false);
} else {
spawned = false;
}
if (spawned) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§dDein Pet wurde gerufen!");
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §eBallons")) {
if (item.getType().toString().endsWith("_WOOL")) {
if (this.activeBalloons.containsKey(player.getUniqueId())) {
((Balloon)this.activeBalloons.get(player.getUniqueId())).remove();
}
this.activeBalloons.put(player.getUniqueId(), new Balloon(player, item.getType()));
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aBallon aktiviert!");
player.closeInventory();
}
} else if (title.equals("§b§lGadgets §8- §dPartikel")) {
if (item.getType() == Material.POPPY) {
this.activeEffects.put(player.getUniqueId(), new ParticleEffect("hearts"));
} else if (item.getType() == Material.BLAZE_POWDER) {
this.activeEffects.put(player.getUniqueId(), new ParticleEffect("flames"));
} else if (item.getType() == Material.WATER_BUCKET) {
this.activeEffects.put(player.getUniqueId(), new ParticleEffect("cloud"));
}
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aPartikel aktiviert!");
player.closeInventory();
} else if (title.equals("§b§lGadgets §8- §6Lustiges")) {
if (item.getType() == Material.EGG) {
ChickenRain.start(player);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§fHühnerregen gestartet!");
player.closeInventory();
} else if (item.getType() == Material.FISHING_ROD) {
player.getInventory().addItem(new ItemStack[]{this.createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren §8(3s CD)")});
player.closeInventory();
} else if (item.getType() == Material.PACKED_ICE) {
player.getInventory().addItem(new ItemStack[]{this.createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Rechtsklick zum Einfrieren §8(10s CD)")});
player.closeInventory();
} else if (item.getType() == Material.GOLDEN_HOE) {
player.getInventory().addItem(new ItemStack[]{this.createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Rechtsklick zum Schießen")});
player.closeInventory();
} else if (item.getType() == Material.FIRE_CHARGE) {
player.getInventory().addItem(new ItemStack[]{this.createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Rechtsklick zum Markieren §8(15s CD)")});
player.closeInventory();
} else if (item.getType() == Material.SHIELD) {
if (this.activeShields.contains(player.getUniqueId())) {
this.activeShields.remove(player.getUniqueId());
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cSchutzzone deaktiviert.");
} else {
this.activeShields.add(player.getUniqueId());
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§5Schutzzone aktiviert!");
}
player.closeInventory();
} else if (item.getType() == Material.FEATHER) {
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), new Runnable() {
int ticks = 0;
public void run() {
if (this.ticks < 20) {
float yaw = player.getLocation().getYaw() + 36.0F;
Location loc = player.getLocation().clone();
loc.setYaw(yaw);
loc.setY(loc.getY() + (double)0.5F);
player.teleport(loc);
player.getWorld().spawnParticle(Particle.CLOUD, loc.clone().add((double)0.0F, (double)1.5F, (double)0.0F), 14, 0.4, 0.2, 0.4, 0.02);
player.setVelocity(player.getVelocity().setY(0.13));
++this.ticks;
}
}
}, 0L, 1L);
player.playSound(player.getLocation(), Sound.ENTITY_PHANTOM_FLAP, 1.0F, 1.2F);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§fMini-Tornado: Du wirbelst herum!");
player.closeInventory();
} else if (item.getType() == Material.TNT) {
Location tntLoc = player.getLocation().add((double)0.0F, (double)0.5F, (double)0.0F);
player.getWorld().spawnParticle(Particle.SMOKE, tntLoc, 16, 0.4, 0.2, 0.4, 0.01);
player.getWorld().playSound(tntLoc, Sound.ENTITY_TNT_PRIMED, 1.0F, 1.0F);
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
player.getWorld().spawnParticle(Particle.EXPLOSION, tntLoc, 2, (double)0.5F, 0.2, (double)0.5F, 0.1);
player.getWorld().playSound(tntLoc, Sound.ENTITY_GENERIC_EXPLODE, 1.0F, 1.1F);
}, 30L);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cFake-TNT gezündet! Keine Sorge, alles harmlos.");
player.closeInventory();
} else if (item.getType() == Material.SLIME_BALL) {
World world = player.getWorld();
Location baseLoc = player.getLocation();
EntityType[] types = new EntityType[]{EntityType.ZOMBIE, EntityType.SKELETON, EntityType.CREEPER, EntityType.SLIME};
final List<LivingEntity> spawned = new ArrayList();
for(int i = 0; i < types.length; ++i) {
Location mobLoc = baseLoc.clone().add(Math.cos((double)i * Math.PI / (double)2.0F) * (double)1.5F, (double)0.0F, Math.sin((double)i * Math.PI / (double)2.0F) * (double)1.5F);
Entity ent = world.spawnEntity(mobLoc, types[i]);
if (ent instanceof LivingEntity) {
LivingEntity mob = (LivingEntity)ent;
mob.setCustomName("§aParty-Mob");
mob.setCustomNameVisible(true);
mob.setAI(false);
mob.setInvulnerable(true);
mob.setCollidable(false);
mob.setSilent(true);
mob.setGlowing(true);
try {
if (mob instanceof Zombie) {
Zombie zombie = (Zombie)mob;
zombie.setBaby(true);
} else if (mob instanceof Slime) {
Slime slime = (Slime)mob;
slime.setSize(1);
}
} catch (Exception var15) {
}
spawned.add(mob);
}
}
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), new Runnable() {
int ticks = 0;
public void run() {
if (this.ticks >= 80) {
spawned.forEach((e) -> e.remove());
} else {
for(LivingEntity mob : spawned) {
mob.getWorld().spawnParticle(Particle.NOTE, mob.getLocation().add((double)0.0F, (double)1.0F, (double)0.0F), 6, 0.3, 0.2, 0.3, 0.01);
mob.teleport(mob.getLocation().add((double)0.0F, 0.08 * Math.sin((double)this.ticks / (double)2.0F), (double)0.0F));
}
++this.ticks;
}
}
}, 0L, 2L);
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_CELEBRATE, 1.0F, 1.2F);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aMob-Party gestartet! Die Mobs tanzen um dich.");
player.closeInventory();
} else if (item.getType() == Material.FIREWORK_ROCKET) {
for(int i = 0; i < 3; ++i) {
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
player.getWorld().spawnParticle(Particle.CRIT, player.getLocation().add((double)0.0F, (double)1.0F, (double)0.0F), 40, 0.8, (double)0.5F, 0.8, 0.15);
player.getWorld().spawnParticle(Particle.NOTE, player.getLocation().add((double)0.0F, (double)1.0F, (double)0.0F), 20, 0.7, 0.3, 0.7, 0.1);
player.playSound(player.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0F, 1.1F);
}, (long)i * 6L);
}
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKonfetti-Kanone: Buntes Konfetti überall!");
player.closeInventory();
} else if (item.getType() == Material.NOTE_BLOCK) {
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), new Runnable() {
int ticks = 0;
public void run() {
if (this.ticks < 40) {
if (this.ticks % 8 == 0) {
player.setVelocity(player.getVelocity().setY(0.4));
player.getWorld().spawnParticle(Particle.NOTE, player.getLocation().add((double)0.0F, (double)2.0F, (double)0.0F), 8, (double)0.5F, 0.2, (double)0.5F, 0.01);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, 1.0F, 1.2F);
}
++this.ticks;
}
}
}, 0L, 2L);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§dTanz-Emote: Du beginnst zu tanzen!");
player.closeInventory();
}
}
}
}
}
private void removeGadgets(Player player) {
UUID uuid = player.getUniqueId();
Integer discoTask = (Integer)this.discoTasks.remove(uuid);
if (discoTask != null) {
Bukkit.getScheduler().cancelTask(discoTask);
}
if (this.activeBalloons.containsKey(player.getUniqueId())) {
((Balloon)this.activeBalloons.get(player.getUniqueId())).remove();
this.activeBalloons.remove(player.getUniqueId());
}
this.activeEffects.remove(player.getUniqueId());
this.activeShields.remove(player.getUniqueId());
PetManager.removePet(player);
FreezeRay.unfreeze(player.getUniqueId());
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(de.nexuslobby.utils.LangManager.getPrefix() + "§cAlle Gadgets abgelegt.");
}
private void fillEdges(Inventory inv) {
ItemStack glass = this.createItem(Material.GRAY_STAINED_GLASS_PANE, " ", (String)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);
}
}
}
private ItemStack createItem(Material mat, String name, String lore) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
List<String> l = new ArrayList();
if (lore != null && !lore.isEmpty()) {
l.add(ChatColor.translateAlternateColorCodes('&', lore));
meta.setLore(l);
}
meta.addItemFlags(new ItemFlag[]{ItemFlag.HIDE_ATTRIBUTES});
meta.addItemFlags(new ItemFlag[]{ItemFlag.HIDE_UNBREAKABLE});
meta.addItemFlags(new ItemFlag[]{ItemFlag.HIDE_ENCHANTS});
item.setItemMeta(meta);
}
return item;
}
public void onDisable() {
HandlerList.unregisterAll(this);
PetManager.unregister();
PetManager.clearAll();
this.activeBalloons.values().forEach(Balloon::remove);
this.activeBalloons.clear();
this.activeEffects.clear();
this.activeShields.clear();
GadgetShield.clear();
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
removeGadgets(player);
UUID uuid = player.getUniqueId();
meteorCooldowns.remove(uuid);
freezeCooldowns.remove(uuid);
grapplingCooldowns.remove(uuid);
}
}
@@ -0,0 +1,45 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.parkour.ParkourManager;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
public class GadgetShield {
private static final Set<UUID> shielded = new HashSet();
public static boolean toggle(Player player) {
UUID uuid = player.getUniqueId();
if (shielded.contains(uuid)) {
shielded.remove(uuid);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cGadget-Schutz §7deaktiviert.");
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 1.0F);
return false;
} else {
shielded.add(uuid);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aGadget-Schutz §7aktiviert. §8Du bist nun immun gegen Gadgets.");
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1.0F, 1.5F);
return true;
}
}
public static boolean isAdminShielded(Player player) {
return shielded.contains(player.getUniqueId());
}
public static boolean isProtected(Player player) {
if (shielded.contains(player.getUniqueId())) {
return true;
} else {
ParkourManager pm = NexusLobby.getInstance().getParkourManager();
return pm != null && pm.isIngame(player);
}
}
public static void clear() {
shielded.clear();
}
}
@@ -0,0 +1,25 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class GrapplingHook {
public static void pullPlayer(Player player, Location target) {
Location playerLoc = player.getLocation();
double distance = target.distance(playerLoc);
if (!(distance < (double)2.0F) && !(distance > (double)50.0F)) {
Vector v = target.toVector().subtract(playerLoc.toVector());
v.multiply(0.3);
v.setY(v.getY() * 0.6 + (double)0.5F);
if (v.length() > (double)2.5F) {
v.normalize().multiply((double)2.5F);
}
player.setVelocity(v);
player.playSound(playerLoc, Sound.ENTITY_WIND_CHARGE_WIND_BURST, 1.0F, 1.2F);
player.playSound(playerLoc, Sound.ITEM_TRIDENT_RIPTIDE_1, 0.5F, 1.5F);
}
}
}
@@ -0,0 +1,24 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class HatManager {
public static void setHat(Player player, Material material, String name) {
ItemStack hat = new ItemStack(material);
ItemMeta meta = hat.getItemMeta();
if (meta != null) {
meta.setDisplayName("§6Hut: " + name);
hat.setItemMeta(meta);
}
player.getInventory().setHelmet(hat);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aDu trägst nun: " + name);
}
public static void removeHat(Player player) {
player.getInventory().setHelmet((ItemStack)null);
}
}
@@ -0,0 +1,54 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
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 = (double)0.0F; d < (double)30.0F; d += (double)0.5F) {
Location point = start.clone().add(direction.clone().multiply(d));
if (point.getBlock().getType().isSolid()) {
target = point;
break;
}
}
if (target != null) {
Location finalTarget = target.clone().add((double)0.0F, (double)0.5F, (double)0.0F);
finalTarget.getWorld().spawnParticle(Particle.FLAME, finalTarget, 20, (double)0.5F, 0.1, (double)0.5F, 0.05);
shooter.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cMeteorit im Anflug...");
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
finalTarget.getWorld().spawnParticle(Particle.EXPLOSION_EMITTER, finalTarget, 1);
finalTarget.getWorld().spawnParticle(Particle.LAVA, finalTarget, 30, (double)0.5F, (double)0.5F, (double)0.5F, 0.1);
finalTarget.getWorld().playSound(finalTarget, Sound.ENTITY_GENERIC_EXPLODE, 1.0F, 0.8F);
for(Entity entity : finalTarget.getWorld().getNearbyEntities(finalTarget, (double)4.0F, (double)4.0F, (double)4.0F)) {
if (entity instanceof Player p) {
if (GadgetShield.isProtected(p)) {
if (GadgetShield.isAdminShielded(p)) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§7Dein Gadget-Schutzschild hat den Meteoriten abgelenkt!");
} else {
p.sendMessage("§8[§6Parkour§8] §7Dein Parkour-Schutzschild hat den Meteoriten abgelenkt!");
}
} else {
Vector v = p.getLocation().toVector().subtract(finalTarget.toVector()).normalize().multiply((double)1.5F).setY((double)0.5F);
p.setVelocity(v);
p.sendMessage("§cBUMM!");
}
}
}
}, 30L);
}
}
}
@@ -0,0 +1,75 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import java.util.Random;
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;
public class PaintballGun {
private static final Random random = new Random();
private static final Material[] COLORS;
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 = (double)0.0F; d < (double)25.0F; d += (double)0.5F) {
Location point = start.clone().add(direction.clone().multiply(d));
point.getWorld().spawnParticle(Particle.ITEM_SNOWBALL, point, 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
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;
for(int x = -radius; x <= radius; ++x) {
for(int y = -radius; y <= radius; ++y) {
for(int z = -radius; z <= radius; ++z) {
if ((double)(x * x + y * y + z * z) <= (double)(radius * radius) + (double)0.5F) {
Block target = centerLoc.clone().add((double)x, (double)y, (double)z).getBlock();
if (target.getType().isSolid()) {
applyColor(target, color);
}
}
}
}
}
}
private static void applyColor(Block block, Material color) {
Location loc = block.getLocation();
loc.getWorld().spawnParticle(Particle.BLOCK, loc.clone().add((double)0.5F, (double)0.5F, (double)0.5F), 3, 0.1, 0.1, 0.1, color.createBlockData());
for(Player online : Bukkit.getOnlinePlayers()) {
online.sendBlockChange(loc, color.createBlockData());
}
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
for(Player online : Bukkit.getOnlinePlayers()) {
online.sendBlockChange(loc, block.getBlockData());
}
}, 400L);
}
static {
COLORS = new Material[]{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};
}
}
@@ -0,0 +1,33 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.entity.Player;
public class ParticleEffect {
private final String type;
private double angle = (double)0.0F;
public ParticleEffect(String type) {
this.type = type;
}
public void update(Player player) {
Location loc = player.getLocation();
switch (this.type.toLowerCase()) {
case "hearts":
player.getWorld().spawnParticle(Particle.HEART, loc.clone().add((double)0.0F, 2.2, (double)0.0F), 1, 0.3, 0.3, 0.3, (double)0.0F);
break;
case "flames":
double x = 0.6 * Math.cos(this.angle);
double z = 0.6 * Math.sin(this.angle);
player.getWorld().spawnParticle(Particle.FLAME, loc.clone().add(x, 0.1, z), 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
this.angle += 0.2;
break;
case "cloud":
player.getWorld().spawnParticle(Particle.CLOUD, loc.clone().add((double)0.0F, (double)2.5F, (double)0.0F), 2, 0.2, 0.05, 0.2, (double)0.0F);
player.getWorld().spawnParticle(Particle.FALLING_WATER, loc.clone().add((double)0.0F, 2.4, (double)0.0F), 1, 0.1, (double)0.0F, 0.1, (double)0.0F);
}
}
}
@@ -0,0 +1,234 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Fox;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.Fox.Type;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
public class PetManager implements Listener {
private static final Map<UUID, Entity> activePets = new HashMap();
private static PetManager instance;
public PetManager() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
}
public static void register() {
if (instance == null) {
instance = new PetManager();
}
}
public static void unregister() {
if (instance != null) {
HandlerList.unregisterAll(instance);
instance = null;
}
}
public static void spawnEntityPet(Player player, String type) {
spawnEntityPet(player, type, false);
}
public static void spawnEntityPet(Player player, String type, boolean baby) {
spawnEntityPet(player, type, baby, (String)null);
}
public static void spawnEntityPet(Player player, String type, boolean baby, String variantName) {
removePet(player);
EntityType entityType;
try {
entityType = EntityType.valueOf(type);
} catch (IllegalArgumentException var12) {
player.sendMessage("§cFehler: Tier-Typ nicht gefunden.");
return;
}
Location loc = player.getLocation();
Entity pet = player.getWorld().spawnEntity(loc, entityType);
String agePrefix = baby ? "§bBaby " : "";
String petName = buildPetDisplayName(type, variantName);
pet.setCustomName(agePrefix + petName);
pet.setCustomNameVisible(true);
pet.setInvulnerable(true);
pet.setPersistent(false);
applyNeutralPetTeam(pet);
if (pet instanceof LivingEntity le) {
le.setRemoveWhenFarAway(false);
if (le instanceof Ageable ageable) {
if (baby) {
ageable.setBaby();
} else {
ageable.setAdult();
}
}
if (le instanceof Wolf && variantName != null && !variantName.isEmpty()) {
Wolf.Variant variant = resolveWolfVariant(variantName);
if (variant != null) {
((Wolf)le).setVariant(variant);
}
}
if (le instanceof Fox && variantName != null && !variantName.isEmpty()) {
try {
((Fox)le).setFoxType(Type.valueOf(variantName));
} catch (IllegalArgumentException var11) {
}
}
if (le instanceof Tameable) {
((Tameable)le).setTamed(true);
((Tameable)le).setOwner(player);
}
}
activePets.put(player.getUniqueId(), pet);
}
public static void updatePets() {
for(Map.Entry<UUID, Entity> entry : activePets.entrySet()) {
Player owner = Bukkit.getPlayer((UUID)entry.getKey());
Entity pet = (Entity)entry.getValue();
if (owner != null && owner.isOnline() && !pet.isDead()) {
if (pet.getWorld().equals(owner.getWorld()) && !(pet.getLocation().distance(owner.getLocation()) > (double)10.0F)) {
if (pet.getLocation().distance(owner.getLocation()) > (double)3.0F) {
Location target = owner.getLocation().clone().add(owner.getLocation().getDirection().multiply((double)-1.5F));
target.setY(owner.getLocation().getY());
pet.teleport(pet.getLocation().add(target.toVector().subtract(pet.getLocation().toVector()).normalize().multiply(0.2)));
Location lookAt = pet.getLocation();
lookAt.setDirection(owner.getLocation().toVector().subtract(pet.getLocation().toVector()));
pet.teleport(lookAt);
}
} else {
pet.teleport(owner.getLocation());
}
}
}
}
public static void removePet(Player player) {
if (activePets.containsKey(player.getUniqueId())) {
Entity pet = (Entity)activePets.get(player.getUniqueId());
removeFromPetTeam(pet);
pet.remove();
activePets.remove(player.getUniqueId());
}
}
public static void clearAll() {
for(Entity pet : activePets.values()) {
removeFromPetTeam(pet);
pet.remove();
}
activePets.clear();
}
private static String capitalize(String str) {
if (str != null && !str.isEmpty()) {
String var10000 = str.substring(0, 1).toUpperCase();
return var10000 + str.substring(1);
} else {
return str;
}
}
private static String buildPetDisplayName(String type, String variantName) {
String baseType = capitalize(type.toLowerCase());
if (variantName != null && !variantName.isEmpty()) {
String variant = capitalize(variantName.toLowerCase());
return variant + " " + baseType;
} else {
return baseType;
}
}
private static Wolf.Variant resolveWolfVariant(String variantName) {
String keyName = variantName.toLowerCase();
NamespacedKey key = NamespacedKey.minecraft(keyName);
return (Wolf.Variant)Registry.WOLF_VARIANT.get(key);
}
private static Team getOrCreatePetTeam() {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
Team team = scoreboard.getTeam("nexus_pets");
if (team == null) {
team = scoreboard.registerNewTeam("nexus_pets");
team.setPrefix("");
team.setSuffix("");
}
return team;
}
private static void applyNeutralPetTeam(Entity pet) {
Team team = getOrCreatePetTeam();
String entry = pet.getUniqueId().toString();
if (!team.hasEntry(entry)) {
team.addEntry(entry);
}
}
private static void removeFromPetTeam(Entity pet) {
if (pet != null) {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
Team team = scoreboard.getTeam("nexus_pets");
if (team != null) {
String entry = pet.getUniqueId().toString();
if (team.hasEntry(entry)) {
team.removeEntry(entry);
}
}
}
}
@EventHandler
public void onPetDamage(EntityDamageEvent event) {
if (activePets.containsValue(event.getEntity())) {
event.setCancelled(true);
}
}
@EventHandler
public void onPetTarget(EntityTargetEvent event) {
if (activePets.containsValue(event.getEntity())) {
event.setCancelled(true);
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
removePet(event.getPlayer());
}
}
@@ -0,0 +1,41 @@
package de.nexuslobby.modules.gadgets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
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 ShieldTask {
private static final Map<UUID, Long> pushCooldowns = new HashMap();
private static final long PUSH_COOLDOWN_MS = 3000L;
public static void handleShield(Player owner) {
for(double i = (double)0.0F; i < (Math.PI * 2D); i += (Math.PI / 8D)) {
double x = Math.cos(i) * 2.2;
double z = Math.sin(i) * 2.2;
owner.getWorld().spawnParticle(Particle.WITCH, owner.getLocation().add(x, (double)0.5F, z), 1, (double)0.0F, (double)0.0F, (double)0.0F, (double)0.0F);
}
long now = System.currentTimeMillis();
for(Entity entity : owner.getNearbyEntities(2.2, (double)2.0F, 2.2)) {
if (entity instanceof Player target) {
if (entity != owner && !GadgetShield.isProtected(target)) {
long lastPush = (Long)pushCooldowns.getOrDefault(target.getUniqueId(), 0L);
if (now - lastPush >= 3000L) {
pushCooldowns.put(target.getUniqueId(), now);
Vector direction = target.getLocation().toVector().subtract(owner.getLocation().toVector()).normalize();
direction.multiply(0.4).setY(0.2);
target.setVelocity(direction);
target.playSound(target.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5F, 0.5F);
}
}
}
}
}
}
@@ -0,0 +1,90 @@
package de.nexuslobby.commands;
import de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class HoloCommand implements CommandExecutor {
private final HologramModule module;
public HoloCommand(HologramModule module) {
this.module = module;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) return true;
if (!player.hasPermission("nexuslobby.hologram")) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKeine Rechte.");
return true;
}
if (args.length < 1) {
sendHelp(player);
return true;
}
if (args[0].equalsIgnoreCase("create")) {
// Wir brauchen id, typ und mindestens ein Wort Text
if (args.length < 4) {
player.sendMessage("§cNutze: /holo create <id> <NONE|FAST|SLOW> <text>");
return true;
}
String id = args[1];
// Der Typ (args[2]) wird im neuen System ignoriert, da wir Seiten nutzen
StringBuilder sb = new StringBuilder();
for (int i = 3; i < args.length; i++) {
sb.append(args[i]).append(i == args.length - 1 ? "" : " ");
}
String fullText = sb.toString().trim();
List<String> pages = new ArrayList<>();
// Support für mehrere Seiten via ";"
if (fullText.contains(";")) {
pages.addAll(Arrays.asList(fullText.split(";")));
} else {
pages.add(fullText);
}
// Aufruf der neuen Methode mit List<String>
module.createHologram(id, player.getLocation(), pages);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aHologramm §e" + id + " §aerstellt (" + pages.size() + " Seiten).");
}
else if (args[0].equalsIgnoreCase("delete")) {
if (args.length < 2) {
player.sendMessage("§cBitte gib eine ID an: /holo delete <id>");
return true;
}
module.removeHologram(args[1]);
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cHologramm §e" + args[1] + " §agelöscht.");
}
else if (args[0].equalsIgnoreCase("reload")) {
module.reloadHolograms();
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aHologramme neu geladen.");
} else {
sendHelp(player);
}
return true;
}
private void sendHelp(Player player) {
player.sendMessage("§8§m-----------§r §6Hologramme §8§m-----------");
player.sendMessage("§e/holo create <id> <NONE|FAST|SLOW> <text>");
player.sendMessage("§e/holo delete <id>");
player.sendMessage("§e/holo reload");
player.sendMessage("§7Nutze §b; §7für neue Seiten.");
player.sendMessage("§7Nutze §b\\n §7für Zeilenumbruch.");
}
}
@@ -0,0 +1,221 @@
package de.nexuslobby.modules.hologram;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Interaction;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HologramModule implements Module, Listener {
private final Map<String, NexusHologram> holograms = new ConcurrentHashMap<>();
private File file;
private FileConfiguration config;
@Override
public String getName() { return "Holograms"; }
@Override
public void onEnable() {
loadConfig();
loadHolograms();
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
// Render-Task: Prüft alle 5 Ticks Sichtbarkeit und Placeholder-Updates
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
holograms.values().forEach(h -> h.renderForPlayer(player));
}
}, 20L, 5L);
}
private void loadConfig() {
file = new File(NexusLobby.getInstance().getDataFolder(), "holograms.yml");
config = YamlConfiguration.loadConfiguration(file);
}
private void loadHolograms() {
// Vorherige Instanzen säubern
holograms.values().forEach(NexusHologram::removeAll);
holograms.clear();
for (String id : config.getKeys(false)) {
String worldName = config.getString(id + ".world");
if (worldName == null) continue;
World world = Bukkit.getWorld(worldName);
if (world == null) continue;
Location loc = new Location(world,
config.getDouble(id + ".x"),
config.getDouble(id + ".y"),
config.getDouble(id + ".z"));
List<String> pages;
if (config.isList(id + ".text")) {
pages = config.getStringList(id + ".text");
} else {
pages = new ArrayList<>();
pages.add(config.getString(id + ".text", "No Text"));
}
holograms.put(id, new NexusHologram(id, loc, pages));
}
}
@EventHandler
public void onInteract(PlayerInteractEntityEvent event) {
// Nur auf Interaction-Entities reagieren (Hologramm-Hitboxen)
if (!(event.getRightClicked() instanceof Interaction)) return;
for (NexusHologram holo : holograms.values()) {
if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) {
holo.nextPage(event.getPlayer());
break;
}
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
// Cleanup alter Entity-Reste, die eventuell noch in der Welt schweben
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
Player p = event.getPlayer();
for (Entity entity : p.getWorld().getEntities()) {
if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) {
// Wenn das Hologramm nicht exakt für diesen Spieler benannt ist -> verstecken
if (!entity.getCustomName().endsWith("_" + p.getName())) {
p.hideEntity(NexusLobby.getInstance(), entity);
}
}
}
}, 10L);
}
public void createHologram(String id, Location loc, List<String> pages) {
// Falls ID bereits existiert, altes Hologramm sauber entfernen
if (holograms.containsKey(id)) {
removeHologram(id);
}
config.set(id + ".world", loc.getWorld().getName());
config.set(id + ".x", loc.getX());
config.set(id + ".y", loc.getY());
config.set(id + ".z", loc.getZ());
config.set(id + ".text", pages);
saveHoloConfig();
NexusHologram holo = new NexusHologram(id, loc, pages);
holograms.put(id, holo);
}
public void removeHologram(String id) {
NexusHologram holo = holograms.remove(id);
if (holo != null) {
// Erst für alle Spieler visuell entfernen
for (Player player : Bukkit.getOnlinePlayers()) {
holo.removeForPlayer(player);
}
// Dann Entities serverseitig löschen
holo.removeAll();
}
config.set(id, null);
saveHoloConfig();
}
/**
* Lädt die holograms.yml neu und rendert alle Hologramme für Online-Spieler neu.
* Wird von HoloCommand (/holo reload) aufgerufen.
*/
public void reloadHolograms() {
// Alle aktiven Hologramme für alle Spieler entfernen
for (Player player : Bukkit.getOnlinePlayers()) {
holograms.values().forEach(h -> h.removeForPlayer(player));
}
// Config neu einlesen und Hologramme neu laden
loadConfig();
loadHolograms();
}
private void saveHoloConfig() {
try {
// Sicherstellen, dass Placeholder-Strings (z.B. %bungeetotal%) korrekt
// gequotet gespeichert werden, damit SnakeYAML sie nicht als YAML-Direktive
// missinterpretiert. Dazu speichern wir die text-Listen explizit als
// String-Werte mit einfachen Anführungszeichen via eigener Serialisierung.
StringBuilder yaml = new StringBuilder();
for (String id : config.getKeys(false)) {
yaml.append(id).append(":\n");
yaml.append(" world: ").append(quoteIfNeeded(config.getString(id + ".world", ""))).append("\n");
yaml.append(" x: ").append(config.getDouble(id + ".x")).append("\n");
yaml.append(" y: ").append(config.getDouble(id + ".y")).append("\n");
yaml.append(" z: ").append(config.getDouble(id + ".z")).append("\n");
yaml.append(" text:\n");
for (String line : config.getStringList(id + ".text")) {
yaml.append(" - ").append(quoteIfNeeded(line)).append("\n");
}
}
java.nio.file.Files.writeString(file.toPath(), yaml.toString());
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!");
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Hologramme: " + e.getMessage());
}
}
/**
* Umschließt Strings mit einfachen Anführungszeichen, wenn sie YAML-Sonderzeichen
* wie '%', ':', '#' enthalten, um Parsing-Fehler zu vermeiden.
*/
private String quoteIfNeeded(String value) {
if (value == null) return "''";
if (value.contains("%") || value.contains(":") || value.contains("#") || value.contains("'")) {
return "'" + value.replace("'", "''") + "'";
}
return value;
}
/**
* WICHTIG: Diese Methode wird vom LobbyTabCompleter benötigt!
* @return Set aller registrierten Hologramm-IDs
*/
public Set<String> getHologramIds() {
return holograms.keySet();
}
@Override
public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
holograms.values().forEach(NexusHologram::removeAll);
holograms.clear();
}
}
@@ -0,0 +1,118 @@
package de.nexuslobby.modules.hologram;
import de.nexuslobby.NexusLobby;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.entity.Display;
import org.bukkit.entity.Interaction;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class NexusHologram {
private final String id;
private final Location location;
private final List<String> pages;
private final Map<UUID, TextDisplay> playerEntities = new ConcurrentHashMap<>();
private final Map<UUID, Interaction> playerInteractions = new ConcurrentHashMap<>();
private final Map<UUID, Integer> currentPage = new ConcurrentHashMap<>();
public NexusHologram(String id, Location location, List<String> pages) {
this.id = id;
this.location = location;
this.pages = pages;
}
public void nextPage(Player player) {
if (pages.size() <= 1) return;
int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % pages.size();
currentPage.put(player.getUniqueId(), next);
renderForPlayer(player);
}
public void renderForPlayer(Player player) {
if (!player.getWorld().equals(location.getWorld()) || player.getLocation().distanceSquared(location) > 2304) {
removeForPlayer(player);
return;
}
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), 0);
if (pageIdx >= pages.size()) pageIdx = 0;
String rawText = pages.get(pageIdx);
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
rawText = PlaceholderAPI.setPlaceholders(player, rawText);
}
final String finalText = rawText.replace("&", "§").replace("\\n", "\n");
TextDisplay display = playerEntities.get(player.getUniqueId());
if (display == null || !display.isValid()) {
// Text-Display erstellen (Hier lassen wir den Namen zur internen Identifikation,
// aber schalten ihn strikt unsichtbar)
display = location.getWorld().spawn(location, TextDisplay.class, entity -> {
entity.setCustomName("nexus_h_" + id + "_" + player.getName());
entity.setCustomNameVisible(false);
entity.setPersistent(false);
entity.setBillboard(Display.Billboard.CENTER);
entity.setBackgroundColor(Color.fromARGB(0, 0, 0, 0));
entity.setText(finalText);
entity.setInvulnerable(true);
});
// Interaction Entity (Hitbox) erstellen
// FIX: WIR SETZEN KEINEN CUSTOM NAME MEHR.
// Das verhindert zu 100%, dass Minecraft etwas anzeigt.
Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> {
entity.setInteractionWidth(2.5f);
entity.setInteractionHeight(2.0f);
entity.setCustomNameVisible(false);
entity.setPersistent(false);
});
// Nur für den Zielspieler sichtbar machen
for (Player other : Bukkit.getOnlinePlayers()) {
if (!other.getUniqueId().equals(player.getUniqueId())) {
other.hideEntity(NexusLobby.getInstance(), display);
other.hideEntity(NexusLobby.getInstance(), interact);
}
}
playerEntities.put(player.getUniqueId(), display);
playerInteractions.put(player.getUniqueId(), interact);
} else {
if (!display.getText().equals(finalText)) {
display.setText(finalText);
}
}
}
public void removeForPlayer(Player player) {
TextDisplay display = playerEntities.remove(player.getUniqueId());
if (display != null) display.remove();
Interaction interact = playerInteractions.remove(player.getUniqueId());
if (interact != null) interact.remove();
}
public void removeAll() {
playerEntities.values().forEach(TextDisplay::remove);
playerInteractions.values().forEach(Interaction::remove);
playerEntities.clear();
playerInteractions.clear();
currentPage.clear();
}
public boolean isInteractionEntity(UUID entityId) {
// Da wir keinen Namen mehr nutzen, verlassen wir uns rein auf die UUID in der Map
return playerInteractions.values().stream().anyMatch(i -> i.getUniqueId().equals(entityId));
}
}
@@ -0,0 +1,365 @@
package de.nexuslobby.modules.intro;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
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.player.PlayerJoinEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class IntroModule implements Module, Listener, CommandExecutor {
private final Set<UUID> activeIntro = new HashSet<>();
private final List<Location> points = new ArrayList<>();
private final List<String> labels = new ArrayList<>();
// Abschluss-Titel (konfigurierbar)
private String endTitle = "§a§lWillkommen!";
private String endSubtitle = "§7Viel Spaß auf NexusLobby";
private File configFile;
private FileConfiguration config;
// --- Einstellungen ---
private final int TICKS_FLUG = 70;
private final int TICKS_PAUSE = 30;
private final int TITLE_FADE_IN = 10;
private final int TITLE_STAY = 50;
private final int TITLE_FADE_OUT = 10;
@Override
public String getName() { return "Intro"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("intro") != null) {
NexusLobby.getInstance().getCommand("intro").setExecutor(this);
}
loadConfig();
}
@Override
public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
activeIntro.clear();
}
// ─────────────────────────────────────────────────────────────────────────
// Persistenz
// ─────────────────────────────────────────────────────────────────────────
private void loadConfig() {
points.clear();
labels.clear();
configFile = new File(NexusLobby.getInstance().getDataFolder(), "intro.yml");
if (!configFile.exists()) {
try { configFile.createNewFile(); } catch (IOException ignored) {}
}
config = YamlConfiguration.loadConfiguration(configFile);
// Abschluss-Titel laden
endTitle = config.getString("end-title", "§a§lWillkommen!");
endSubtitle = config.getString("end-subtitle", "§7Viel Spaß auf NexusLobby");
// Punkte laden
List<?> list = config.getList("intro-points");
if (list != null) {
for (Object obj : list) {
if (obj instanceof Map<?, ?> map) {
Object locObj = map.get("location");
Object labelObj = map.get("label");
if (locObj instanceof Location loc) {
points.add(loc);
labels.add(labelObj != null ? labelObj.toString() : "");
}
}
}
}
// Rückwärts-Kompatibilität
if (points.isEmpty()) {
List<?> oldList = config.getList("points");
if (oldList != null) {
for (Object obj : oldList) {
if (obj instanceof Location loc) {
points.add(loc);
labels.add("");
}
}
}
}
}
private void saveConfig() {
// Abschluss-Titel speichern
config.set("end-title", endTitle);
config.set("end-subtitle", endSubtitle);
// Punkte speichern
List<Map<String, Object>> data = new ArrayList<>();
for (int i = 0; i < points.size(); i++) {
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("location", points.get(i));
entry.put("label", labels.get(i));
data.add(entry);
}
config.set("intro-points", data);
config.set("points", null);
try {
config.save(configFile);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Intro-Config: " + e.getMessage());
}
}
// ─────────────────────────────────────────────────────────────────────────
// Events
// ─────────────────────────────────────────────────────────────────────────
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (!event.getPlayer().hasPlayedBefore() && points.size() >= 2) {
startIntro(event.getPlayer());
}
}
// ─────────────────────────────────────────────────────────────────────────
// Befehle
// ─────────────────────────────────────────────────────────────────────────
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.admin")) return true;
if (args.length == 0) {
sendHelp(p);
return true;
}
switch (args[0].toLowerCase()) {
case "add" -> {
String pointLabel = args.length > 1
? String.join(" ", Arrays.copyOfRange(args, 1, args.length))
: "";
points.add(p.getLocation());
labels.add(pointLabel);
saveConfig();
if (pointLabel.isEmpty()) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aPunkt §e#" + points.size() + " §awurde gesetzt.");
} else {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aPunkt §e#" + points.size() + " §amit Text §e\"" + pointLabel + "\" §agesetzt.");
}
}
case "clear" -> {
points.clear();
labels.clear();
saveConfig();
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cAlle Intro-Punkte wurden gelöscht.");
}
case "list" -> {
if (points.isEmpty()) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKeine Punkte vorhanden.");
return true;
}
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lIntro-Punkte (" + points.size() + ")");
for (int i = 0; i < points.size(); i++) {
Location loc = points.get(i);
String lbl = labels.get(i).isEmpty() ? "§7(kein Text)" : "§e" + labels.get(i);
p.sendMessage("§8#" + (i + 1) + " §7"
+ String.format("%.1f", loc.getX()) + ", "
+ String.format("%.1f", loc.getY()) + ", "
+ String.format("%.1f", loc.getZ())
+ " " + lbl);
}
p.sendMessage("§8§m------------------------------------");
}
case "start" -> {
if (points.size() < 2) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cDu brauchst mindestens 2 Punkte für eine Fahrt.");
} else {
startIntro(p);
}
}
// /intro setendtitle <Text> — setzt den großen Abschluss-Titel
case "setendtitle" -> {
if (args.length < 2) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cBenutzung: §e/intro setendtitle <Text>");
p.sendMessage("§7Farbcodes mit §e& §7möglich. Beispiel: §e/intro setendtitle &a&lWillkommen!");
return true;
}
endTitle = colorize(String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
saveConfig();
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aAbschluss-Titel gesetzt: " + endTitle);
// Vorschau
p.sendTitle(endTitle, endSubtitle, TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT);
}
// /intro setendsubtitle <Text> — setzt den kleinen Abschluss-Untertitel
case "setendsubtitle" -> {
if (args.length < 2) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cBenutzung: §e/intro setendsubtitle <Text>");
p.sendMessage("§7Farbcodes mit §e& §7möglich. Beispiel: §e/intro setendsubtitle &7Viel Spaß!");
return true;
}
endSubtitle = colorize(String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
saveConfig();
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aAbschluss-Untertitel gesetzt: " + endSubtitle);
// Vorschau
p.sendTitle(endTitle, endSubtitle, TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT);
}
default -> sendHelp(p);
}
return true;
}
private void sendHelp(Player p) {
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lNexus Intro System (Cinematic)");
p.sendMessage("§e/intro add [Text] §7- Punkt + optionaler Titel");
p.sendMessage("§e/intro clear §7- Alle Punkte löschen");
p.sendMessage("§e/intro list §7- Alle Punkte anzeigen");
p.sendMessage("§e/intro start §7- Fahrt testen");
p.sendMessage("§e/intro setendtitle <Text> §7- Abschluss-Titel setzen");
p.sendMessage("§e/intro setendsubtitle <Text> §7- Abschluss-Untertitel setzen");
p.sendMessage("§7Aktuell: §r" + endTitle + " §8/ §r" + endSubtitle);
p.sendMessage("§8§m------------------------------------");
}
// ─────────────────────────────────────────────────────────────────────────
// Intro-Logik
// ─────────────────────────────────────────────────────────────────────────
public void startIntro(Player player) {
activeIntro.add(player.getUniqueId());
player.setGameMode(GameMode.SPECTATOR);
// Titel für den ersten Punkt sofort anzeigen
showTitleForSegment(player, 0);
new BukkitRunnable() {
int currentSegment = 0;
int tickInSegment = 0;
boolean isPausing = true;
int lastTitleSegment = 0;
@Override
public void run() {
try {
if (!player.isOnline() || !activeIntro.contains(player.getUniqueId())) {
this.cancel();
return;
}
if (currentSegment >= points.size() - 1) {
stopIntro(player);
this.cancel();
return;
}
Location start = points.get(currentSegment);
Location end = points.get(currentSegment + 1);
if (isPausing) {
player.teleport(start);
tickInSegment++;
if (tickInSegment >= TICKS_PAUSE) {
isPausing = false;
tickInSegment = 0;
}
} else {
double progress = (double) tickInSegment / (double) TICKS_FLUG;
double smoothT = progress * progress * (3 - 2 * progress);
player.teleport(interpolate(start, end, smoothT));
tickInSegment++;
if (tickInSegment >= TICKS_FLUG) {
isPausing = true;
tickInSegment = 0;
currentSegment++;
if (currentSegment < points.size() && currentSegment != lastTitleSegment) {
showTitleForSegment(player, currentSegment);
lastTitleSegment = currentSegment;
}
}
}
} catch (Exception e) {
this.cancel();
stopIntro(player);
}
}
}.runTaskTimer(NexusLobby.getInstance(), 0L, 1L);
}
private void showTitleForSegment(Player player, int segmentIndex) {
if (segmentIndex < 0 || segmentIndex >= labels.size()) return;
String lbl = labels.get(segmentIndex);
if (lbl == null || lbl.isEmpty()) return;
player.sendTitle(
"§6§l" + lbl,
"§7✦ NexusLobby ✦",
TITLE_FADE_IN,
TITLE_STAY,
TITLE_FADE_OUT
);
}
private Location interpolate(Location start, Location end, double t) {
double x = start.getX() + (end.getX() - start.getX()) * t;
double y = start.getY() + (end.getY() - start.getY()) * t;
double z = start.getZ() + (end.getZ() - start.getZ()) * t;
float startYaw = start.getYaw();
float endYaw = end.getYaw();
float diff = (endYaw - startYaw) % 360;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
float yaw = startYaw + diff * (float) t;
float pitch = (float) (start.getPitch() + (end.getPitch() - start.getPitch()) * t);
return new Location(start.getWorld(), x, y, z, yaw, pitch);
}
private void stopIntro(Player player) {
activeIntro.remove(player.getUniqueId());
player.setGameMode(GameMode.ADVENTURE);
player.teleport(player.getWorld().getSpawnLocation());
// Abschluss-Titel — komplett konfigurierbar
player.sendTitle(endTitle, endSubtitle, 10, 60, 20);
}
/** Ersetzt &-Farbcodes durch §-Codes */
private String colorize(String input) {
return input.replace("&", "§");
}
}
@@ -0,0 +1,310 @@
package de.nexuslobby.modules.mapart;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class MapArtModule implements Module, CommandExecutor {
private File storageFile;
private FileConfiguration storageConfig;
// FIX: Unbegrenzter Cache führt bei vielen verschiedenen Bild-URLs zu einem
// OutOfMemoryError. Stattdessen nutzen wir einen LRU-Cache mit max. 50 Einträgen.
// Älteste Kacheln werden automatisch verdrängt.
private static final int MAX_CACHE_SIZE = 50;
private final Map<String, BufferedImage> tileCache = Collections.synchronizedMap(
new LinkedHashMap<String, BufferedImage>(MAX_CACHE_SIZE + 1, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, BufferedImage> eldest) {
return size() > MAX_CACHE_SIZE;
}
}
);
// Hilfs-Set um parallele Downloads der gleichen URL zu verhindern
private final Map<String, Boolean> loadingShield = new ConcurrentHashMap<>();
@Override
public String getName() { return "MapArt"; }
@Override
public void onEnable() {
if (NexusLobby.getInstance().getCommand("mapart") != null) {
NexusLobby.getInstance().getCommand("mapart").setExecutor(this);
}
initStorage();
reloadMaps();
}
@Override
public void onDisable() {
saveStorage();
tileCache.clear();
}
private void initStorage() {
storageFile = new File(NexusLobby.getInstance().getDataFolder(), "mapart.yml");
if (!storageFile.exists()) {
try {
storageFile.createNewFile();
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Erstellen der mapart.yml: " + e.getMessage());
}
}
storageConfig = YamlConfiguration.loadConfiguration(storageFile);
}
private void saveStorage() {
try {
storageConfig.save(storageFile);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der mapart.yml: " + e.getMessage());
}
}
private void reloadMaps() {
ConfigurationSection section = storageConfig.getConfigurationSection("active_maps");
if (section == null) return;
for (String idStr : section.getKeys(false)) {
try {
int mapId = Integer.parseInt(idStr);
String url = section.getString(idStr + ".url");
int x = section.getInt(idStr + ".x");
int y = section.getInt(idStr + ".y");
int totalW = section.getInt(idStr + ".totalW");
int totalH = section.getInt(idStr + ".totalH");
@SuppressWarnings("deprecation")
MapView view = Bukkit.getMap(mapId);
if (view != null) {
applyRenderer(view, url, x, y, totalW, totalH);
}
} catch (Exception ignored) {}
}
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) return true;
if (!player.hasPermission("nexuslobby.mapart")) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKeine Rechte.");
return true;
}
if (args.length == 0) {
sendHelp(player);
return true;
}
if (args[0].equalsIgnoreCase("delete")) {
int radius = 3;
if (args.length == 2) {
try {
radius = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cUngültiger Radius.");
return true;
}
}
deleteNearbyMaps(player, radius);
return true;
}
if (args.length != 2) {
sendHelp(player);
return true;
}
RayTraceResult rayTrace = player.rayTraceBlocks(5);
if (rayTrace == null || rayTrace.getHitBlock() == null || rayTrace.getHitBlockFace() == null) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cBitte schaue eine Wand direkt an.");
return true;
}
Block targetBlock = rayTrace.getHitBlock();
BlockFace hitFace = rayTrace.getHitBlockFace();
if (hitFace == BlockFace.UP || hitFace == BlockFace.DOWN) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cAktuell nur an vertikalen Wänden möglich.");
return true;
}
String url = args[0];
int width, height;
try {
String[] parts = args[1].toLowerCase().split("x");
width = Integer.parseInt(parts[0]);
height = Integer.parseInt(parts[1]);
} catch (Exception e) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cUngültiges Format (z.B. 6x4).");
return true;
}
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§7Bild wird verarbeitet...");
createRaster(player, targetBlock, hitFace, url, width, height);
return true;
}
private void sendHelp(Player p) {
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lNexus MapArt");
p.sendMessage("§e/mapart <URL> <BxH> §7- Bild erstellen");
p.sendMessage("§e/mapart delete [Radius] §7- Bilder in der Nähe löschen");
p.sendMessage("§8§m------------------------------------");
}
private void deleteNearbyMaps(Player player, int radius) {
AtomicInteger count = new AtomicInteger(0);
player.getNearbyEntities(radius, radius, radius).forEach(entity -> {
if (entity instanceof ItemFrame frame) {
ItemStack item = frame.getItem();
if (item != null && item.getType() == Material.FILLED_MAP && item.getItemMeta() instanceof MapMeta meta) {
if (meta.hasMapView()) {
int id = meta.getMapView().getId();
if (storageConfig.contains("active_maps." + id)) {
storageConfig.set("active_maps." + id, null);
frame.remove();
count.incrementAndGet();
}
}
}
}
});
saveStorage();
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aErfolgreich §e" + count.get() + " §aKartenelemente gelöscht.");
}
private void createRaster(Player player, Block startBlock, BlockFace face, String url, int gridW, int gridH) {
BlockFace rightDirection;
switch (face) {
case NORTH: rightDirection = BlockFace.WEST; break;
case SOUTH: rightDirection = BlockFace.EAST; break;
case EAST: rightDirection = BlockFace.NORTH; break;
case WEST: rightDirection = BlockFace.SOUTH; break;
default: rightDirection = BlockFace.EAST;
}
Block origin = startBlock.getRelative(face);
for (int y = 0; y < gridH; y++) {
for (int x = 0; x < gridW; x++) {
Block currentPos = origin.getRelative(rightDirection, x).getRelative(BlockFace.DOWN, y);
spawnPersistentFrame(player, currentPos, face, url, x, y, gridW, gridH);
}
}
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§aBild-Raster wurde permanent platziert!");
}
private void spawnPersistentFrame(Player player, Block pos, BlockFace face, String url, int x, int y, int totalW, int totalH) {
MapView view = Bukkit.createMap(player.getWorld());
applyRenderer(view, url, x, y, totalW, totalH);
String path = "active_maps." + view.getId();
storageConfig.set(path + ".url", url);
storageConfig.set(path + ".x", x);
storageConfig.set(path + ".y", y);
storageConfig.set(path + ".totalW", totalW);
storageConfig.set(path + ".totalH", totalH);
saveStorage();
ItemStack mapStack = new ItemStack(Material.FILLED_MAP);
MapMeta meta = (MapMeta) mapStack.getItemMeta();
if (meta != null) {
meta.setMapView(view);
mapStack.setItemMeta(meta);
}
try {
ItemFrame frame = player.getWorld().spawn(pos.getLocation(), ItemFrame.class);
frame.setFacingDirection(face);
frame.setItem(mapStack);
frame.setVisible(false);
frame.setFixed(true);
} catch (Exception ignored) {}
}
private void applyRenderer(MapView view, String url, int tileX, int tileY, int totalW, int totalH) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(new MapRenderer() {
private final String cacheKey = url + "_" + totalW + "x" + totalH + "_" + tileX + "_" + tileY;
private boolean errorLogged = false;
@Override
public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player p) {
// 1. Wenn Kachel im Cache, sofort zeichnen (extrem schnell)
if (tileCache.containsKey(cacheKey)) {
canvas.drawImage(0, 0, tileCache.get(cacheKey));
return;
}
// 2. Wenn bereits ein Thread für diese URL lädt, abbrechen (verhindert Spam)
if (loadingShield.containsKey(url)) return;
// 3. Bild asynchron laden
loadingShield.put(url, true);
Bukkit.getScheduler().runTaskAsynchronously(NexusLobby.getInstance(), () -> {
try {
BufferedImage original = ImageIO.read(new URL(url));
if (original != null) {
int targetW = totalW * 128;
int targetH = totalH * 128;
BufferedImage fullScaled = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = fullScaled.createGraphics();
g.drawImage(original.getScaledInstance(targetW, targetH, Image.SCALE_SMOOTH), 0, 0, null);
g.dispose();
// Alle benötigten Kacheln für dieses Bild in den Cache legen
for (int ty = 0; ty < totalH; ty++) {
for (int tx = 0; tx < totalW; tx++) {
String key = url + "_" + totalW + "x" + totalH + "_" + tx + "_" + ty;
tileCache.put(key, fullScaled.getSubimage(tx * 128, ty * 128, 128, 128));
}
}
}
} catch (Exception e) {
if (!errorLogged) {
NexusLobby.getInstance().getLogger().warning("Fehler beim Laden von MapArt: " + url);
errorLogged = true;
}
} finally {
loadingShield.remove(url);
}
});
}
});
}
}
@@ -0,0 +1,107 @@
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;
}
@EventHandler
public void onInteract(PlayerInteractAtEntityEvent event) {
if (!(event.getRightClicked() instanceof ArmorStand as)) return;
if (!as.getScoreboardTags().contains(NPC_TAG)) return;
Player player = event.getPlayer();
event.setCancelled(true);
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;
}
}
manager.startParkour(player, as.getLocation());
// Strecken-Nummer dem Spieler mitteilen
int track = manager.getActiveTrack(player);
if (track != -1) {
player.sendMessage("§8[§6Parkour§8] §eViel Erfolg auf §bStrecke " + track + "§e! Erreiche das Ziel so schnell wie möglich.");
}
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f);
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
// Performance: Nur bei Block-Wechsel berechnen
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 ---
// Absturz-Y aus config lesen (Standard: 50), damit Maps unter Y=50 korrekt funktionieren
double fallY = NexusLobby.getInstance().getConfig().getDouble("parkour.fall_y", 50.0);
if (loc.getY() < fallY) {
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 (Track-spezifisch) ---
List<Location> cps = manager.getOrderedCheckpoints(player);
for (int i = 0; i < cps.size(); i++) {
if (isNearby(loc, cps.get(i))) {
manager.reachCheckpoint(player, i);
}
}
// --- 3. ZIEL-CHECK (Track-spezifisch) ---
Location finish = manager.getFinishLocation(player);
if (finish != null && isNearby(loc, finish)) {
manager.finishParkour(player);
startCooldown.put(player.getUniqueId(), System.currentTimeMillis());
}
}
private boolean isNearby(Location playerLoc, Location targetLoc) {
if (playerLoc.getWorld() == null || !playerLoc.getWorld().equals(targetLoc.getWorld())) return false;
return playerLoc.distanceSquared(targetLoc) <= 2.25; // ~1.5 Blöcke Radius
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
manager.stopParkour(event.getPlayer());
startCooldown.remove(uuid);
}
}
@@ -0,0 +1,464 @@
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;
/**
* ParkourManager – unterstützt zwei Strecken (Track 1 & 2).
*
* Config-Struktur (parkour.yml):
* tracks:
* 1:
* start: <Location>
* finish: <Location>
* checkpoints:
* 1: <Location>
* 2: <Location>
* ...
* 2:
* start: <Location>
* finish: <Location>
* checkpoints:
* 1: <Location>
* ...
* besttimes:
* <uuid>: <seconds>
* names:
* <uuid>: <playerName>
*
* Beim Start wird zufällig eine der beiden konfigurierten Strecken ausgewählt.
* Beide Strecken müssen die gleiche Anzahl an Checkpoints haben – andernfalls
* wird nur die vollständige gewählt.
*/
public class ParkourManager {
private final NexusLobby plugin;
private final File file;
private FileConfiguration config;
// Aktive Läufe
private final Map<UUID, Long> startTime = new HashMap<>();
private final Map<UUID, Location> lastCheckpointLoc = new HashMap<>();
private final Map<UUID, Integer> nextCheckpointIndex= new HashMap<>();
/** Welche Track-Nummer (1 oder 2) der Spieler gerade läuft */
private final Map<UUID, Integer> activeTrack = new HashMap<>();
private final Random random = new Random();
public ParkourManager(NexusLobby plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "parkour.yml");
loadConfig();
migrateLegacyBestTimes();
startParticleTask();
}
// ─────────────────────────────────────────────────────────────────────────
// Config I/O
// ─────────────────────────────────────────────────────────────────────────
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);
}
/**
* Migriert Bestzeiten vom alten Format (besttimes.<uuid>) ins neue Format
* (besttimes.<track>.<uuid>). Alte Zeiten werden Track 1 zugeordnet, da vor
* dieser Änderung nur ein Track existierte.
* Läuft einmalig beim Start; tut nichts wenn keine alten Daten vorhanden sind.
*/
private void migrateLegacyBestTimes() {
if (!config.contains("besttimes")) return;
// Prüfen ob es direkte UUID-Keys unter besttimes gibt (altes Format)
// Im neuen Format wären hier nur "1" und "2" als Keys
var section = config.getConfigurationSection("besttimes");
if (section == null) return;
int migrated = 0;
for (String key : new java.util.HashSet<>(section.getKeys(false))) {
// Neue Keys sind "1" oder "2" – alte Keys sind UUID-Strings (36 Zeichen, enthalten "-")
if (!key.equals("1") && !key.equals("2") && key.contains("-")) {
double time = config.getDouble("besttimes." + key);
// Nur migrieren wenn unter besttimes.1.<uuid> noch kein besserer Wert steht
String newPath = "besttimes.1." + key;
double existingTime = config.getDouble(newPath, 99999.9);
if (time < existingTime) {
config.set(newPath, time);
}
// Alten Key entfernen
config.set("besttimes." + key, null);
migrated++;
}
}
if (migrated > 0) {
save();
plugin.getLogger().info("[Parkour] Migration: " + migrated
+ " Bestzeit(en) von altem Format nach Strecke 1 übertragen.");
}
}
private void save() {
try {
config.save(file);
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern der Parkour-Config: " + e.getMessage());
}
}
// ─────────────────────────────────────────────────────────────────────────
// Setup-Befehle (Admin)
// ─────────────────────────────────────────────────────────────────────────
public void setStartLocation(Player player, int track) {
config.set("tracks." + track + ".start", player.getLocation());
save();
player.sendMessage("§8[§6Parkour§8] §7Start von §eStrecke " + track + " §7an deiner Position gesetzt.");
}
public void setFinishLocation(Player player, int track) {
config.set("tracks." + track + ".finish", player.getLocation());
save();
player.sendMessage("§8[§6Parkour§8] §7Ziel von §eStrecke " + track + " §7an deiner Position gesetzt.");
}
public void setCheckpoint(Player player, int track) {
String cpBase = "tracks." + track + ".checkpoints";
int nextId = 1;
if (config.contains(cpBase) && config.getConfigurationSection(cpBase) != null) {
nextId = config.getConfigurationSection(cpBase).getKeys(false).size() + 1;
}
config.set(cpBase + "." + nextId, player.getLocation());
save();
player.sendMessage("§8[§6Parkour§8] §7Checkpoint §e#" + nextId + " §7(Strecke " + track + ") §7gesetzt.");
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1.0f, 1.5f);
}
/**
* Löscht alle Punkte beider Strecken und bricht laufende Runs ab.
*/
public void removeAllPoints() {
config.set("tracks", null);
save();
for (UUID uuid : new HashSet<>(startTime.keySet())) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) {
stopParkour(p);
p.sendMessage("§8[§6Parkour§8] §cDie Strecken wurden soeben gelöscht.");
}
}
}
// ─────────────────────────────────────────────────────────────────────────
// Getter (Track-spezifisch)
// ─────────────────────────────────────────────────────────────────────────
public Location getStartLocation(int track) {
return config.getLocation("tracks." + track + ".start");
}
public Location getFinishLocation(int track) {
return config.getLocation("tracks." + track + ".finish");
}
public List<Location> getOrderedCheckpoints(int track) {
String cpBase = "tracks." + track + ".checkpoints";
if (!config.contains(cpBase) || config.getConfigurationSection(cpBase) == null)
return new ArrayList<>();
return config.getConfigurationSection(cpBase).getKeys(false).stream()
.sorted(Comparator.comparingInt(Integer::parseInt))
.map(key -> config.getLocation(cpBase + "." + key))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* Gibt die aktive Track-Nummer des Spielers zurück (1 oder 2), oder -1 wenn nicht aktiv.
*/
public int getActiveTrack(Player player) {
return activeTrack.getOrDefault(player.getUniqueId(), -1);
}
// Convenience-Wrapper für ParkourListener (nutzt den aktiven Track des Spielers)
public List<Location> getOrderedCheckpoints(Player player) {
return getOrderedCheckpoints(getActiveTrack(player));
}
public Location getFinishLocation(Player player) {
return getFinishLocation(getActiveTrack(player));
}
// ─────────────────────────────────────────────────────────────────────────
// Parkour-Ablauf
// ─────────────────────────────────────────────────────────────────────────
/**
* Startet den Parkour für den Spieler.
* Es wird zufällig eine der beiden Strecken gewählt, sofern beide vollständig
* konfiguriert sind (Start + Finish + mindestens gleiche Checkpoint-Anzahl).
* Falls nur eine Strecke konfiguriert ist, wird diese gewählt.
*/
public void startParkour(Player player, Location npcLocation) {
if (startTime.containsKey(player.getUniqueId())) return;
int chosenTrack = pickRandomTrack();
if (chosenTrack == -1) {
player.sendMessage("§8[§6Parkour§8] §cKeine Strecke konfiguriert. Bitte einen Admin kontaktieren.");
return;
}
startTime.put(player.getUniqueId(), System.currentTimeMillis());
lastCheckpointLoc.put(player.getUniqueId(), npcLocation);
nextCheckpointIndex.put(player.getUniqueId(), 0);
activeTrack.put(player.getUniqueId(), chosenTrack);
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);
}
/**
* Wählt zufällig einen validen Track (1 oder 2).
* Bevorzugt Tracks mit gleichem Checkpoint-Count; fällt auf jeden konfigurierten zurück.
* Gibt -1 zurück, wenn kein Track konfiguriert ist.
*/
private int pickRandomTrack() {
boolean t1 = isTrackReady(1);
boolean t2 = isTrackReady(2);
if (t1 && t2) {
// Beide vollständig – prüfen ob gleiche Länge
int len1 = getOrderedCheckpoints(1).size();
int len2 = getOrderedCheckpoints(2).size();
if (len1 != len2) {
// Warnung ins Log, aber trotzdem zufällig wählen
plugin.getLogger().warning("[Parkour] Strecke 1 hat " + len1 + " Checkpoints, Strecke 2 hat " + len2 + ". Bitte angleichen!");
}
return random.nextBoolean() ? 1 : 2;
} else if (t1) {
return 1;
} else if (t2) {
return 2;
}
return -1;
}
/** Gibt zurück, ob Start, Finish und mindestens 1 Checkpoint für den Track gesetzt sind. */
public boolean isTrackReady(int track) {
return getStartLocation(track) != null
&& getFinishLocation(track) != null
&& !getOrderedCheckpoints(track).isEmpty();
}
private void startParticleTask() {
new BukkitRunnable() {
@Override
public void run() {
for (UUID uuid : startTime.keySet()) {
Player p = Bukkit.getPlayer(uuid);
if (p == null) continue;
int track = activeTrack.getOrDefault(uuid, 1);
int nextIdx = nextCheckpointIndex.getOrDefault(uuid, 0);
List<Location> cps = getOrderedCheckpoints(track);
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(track);
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) return;
int track = activeTrack.getOrDefault(player.getUniqueId(), 1);
List<Location> cps = getOrderedCheckpoints(track);
if (reachedIndex >= cps.size()) return;
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;
int track = activeTrack.getOrDefault(player.getUniqueId(), 1);
if (nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0) < getOrderedCheckpoints(track).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! §8(Strecke " + track + ")");
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());
activeTrack.remove(player.getUniqueId());
}
// ─────────────────────────────────────────────────────────────────────────
// Bestzeiten
// ─────────────────────────────────────────────────────────────────────────
private void saveBestTime(Player player, double time) {
int track = activeTrack.getOrDefault(player.getUniqueId(), 1);
String path = "besttimes." + track + "." + 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.");
}
}
/**
* Löscht NUR die Bestzeiten-Liste (besttimes + names).
* Die Strecken-Konfiguration (tracks) bleibt vollständig erhalten.
* Wird durch "/nexus parkour clear" aufgerufen.
*/
public void clearStats() {
config.set("besttimes", null);
config.set("names", null);
save();
}
public void clearStats(int track) {
config.set("besttimes." + track, null);
save();
}
public String getTopTen() {
// Fallback: kombiniert beide Tracks (Legacy-Support für %nexuslobby_parkour_top%)
return getTopTen(0);
}
public String getTopTen(int track) {
String trackLabel = track == 1 ? " §8(Strecke 1)" : track == 2 ? " §8(Strecke 2)" : "";
Map<String, Double> allTimes = new HashMap<>();
if (track == 0) {
// Alle Tracks kombiniert (nimmt die beste Zeit je Spieler über alle Tracks)
for (int t = 1; t <= 2; t++) {
String section = "besttimes." + t;
if (config.contains(section) && config.getConfigurationSection(section) != null) {
for (String uuidStr : config.getConfigurationSection(section).getKeys(false)) {
double time = config.getDouble(section + "." + uuidStr);
allTimes.merge(uuidStr, time, Math::min);
}
}
}
} else {
String section = "besttimes." + track;
if (!config.contains(section) || config.getConfigurationSection(section) == null) {
return "§6§l🏆 TOP 10 PARKOUR" + trackLabel + "\n§7Noch keine Rekorde.";
}
for (String uuidStr : config.getConfigurationSection(section).getKeys(false)) {
allTimes.put(uuidStr, config.getDouble(section + "." + uuidStr));
}
}
if (allTimes.isEmpty())
return "§6§l🏆 TOP 10 PARKOUR" + trackLabel + "\n§7Noch keine Rekorde.";
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" + trackLabel);
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();
}
// ─────────────────────────────────────────────────────────────────────────
// Hilfsmethoden
// ─────────────────────────────────────────────────────────────────────────
public boolean isIngame(Player player) { return startTime.containsKey(player.getUniqueId()); }
public Location getCheckpoint(Player player) { return lastCheckpointLoc.get(player.getUniqueId()); }
/** Gibt die Strecken-Info als lesbaren String aus (für Admin-Feedback). */
public String getTrackInfo() {
StringBuilder sb = new StringBuilder("§8[§6Parkour§8] §7Track-Status:\n");
for (int t = 1; t <= 2; t++) {
int cps = getOrderedCheckpoints(t).size();
boolean ready = isTrackReady(t);
sb.append(" §eStrecke ").append(t).append(": ")
.append(ready ? "§a✔" : "§c✘")
.append(" §7(").append(cps).append(" Checkpoints)");
if (t < 2) sb.append("\n");
}
return sb.toString();
}
}
@@ -0,0 +1,196 @@
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() {
org.bukkit.event.HandlerList.unregisterAll(this);
}
@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;
}
}
@@ -5,13 +5,53 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class PortalCommand implements CommandExecutor { import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PortalCommand implements CommandExecutor, Listener {
private final PortalManager portalManager; private final PortalManager portalManager;
// Statische Maps, damit wir von überall (z.B. BorderCommand) auf die Auswahl zugreifen können
private static final Map<UUID, Location> selection1 = new HashMap<>();
private static final Map<UUID, Location> selection2 = new HashMap<>();
public PortalCommand(PortalManager portalManager) { public PortalCommand(PortalManager portalManager) {
this.portalManager = portalManager; this.portalManager = portalManager;
// FIX: Listener registrieren, damit Selektionen beim Verlassen bereinigt werden
de.nexuslobby.NexusLobby.getInstance().getServer().getPluginManager()
.registerEvents(this, de.nexuslobby.NexusLobby.getInstance());
}
// FIX: Selektionen beim Quit entfernen – statische Maps wuchsen sonst ewig
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
selection1.remove(uuid);
selection2.remove(uuid);
}
// Statische Hilfsmethoden für andere Klassen
public static Location getSelection1(Player player) {
return selection1.get(player.getUniqueId());
}
public static Location getSelection2(Player player) {
return selection2.get(player.getUniqueId());
}
// Methoden zum Setzen (für deinen Wand-Listener oder Befehle)
public static void setSelection1(Player player, Location loc) {
selection1.put(player.getUniqueId(), loc);
}
public static void setSelection2(Player player, Location loc) {
selection2.put(player.getUniqueId(), loc);
} }
@Override @Override
@@ -23,13 +63,11 @@ public class PortalCommand implements CommandExecutor {
Player p = (Player) sender; Player p = (Player) sender;
// Wenn keine Argumente da sind, Hilfe zeigen
if (args.length == 0) { if (args.length == 0) {
sendHelp(p); sendHelp(p);
return true; return true;
} }
// Switch case für die Unterbefehle
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
case "create": case "create":
if (args.length < 3) { if (args.length < 3) {
@@ -46,7 +84,9 @@ public class PortalCommand implements CommandExecutor {
case "setpos1": case "setpos1":
if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos1 <Name>"); return true; } if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos1 <Name>"); return true; }
if (portalManager.setPortalPos(args[1], 1, p.getLocation())) { Location loc1 = p.getLocation();
if (portalManager.setPortalPos(args[1], 1, loc1)) {
setSelection1(p, loc1); // Speichert es auch in der statischen Map für die Border
p.sendMessage("§aPosition 1 gesetzt für " + args[1]); p.sendMessage("§aPosition 1 gesetzt für " + args[1]);
} else { } else {
p.sendMessage("§cPortal nicht gefunden."); p.sendMessage("§cPortal nicht gefunden.");
@@ -55,7 +95,9 @@ public class PortalCommand implements CommandExecutor {
case "setpos2": case "setpos2":
if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos2 <Name>"); return true; } if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos2 <Name>"); return true; }
if (portalManager.setPortalPos(args[1], 2, p.getLocation())) { Location loc2 = p.getLocation();
if (portalManager.setPortalPos(args[1], 2, loc2)) {
setSelection2(p, loc2); // Speichert es auch in der statischen Map für die Border
p.sendMessage("§aPosition 2 gesetzt für " + args[1]); p.sendMessage("§aPosition 2 gesetzt für " + args[1]);
} else { } else {
p.sendMessage("§cPortal nicht gefunden."); p.sendMessage("§cPortal nicht gefunden.");
@@ -65,12 +107,9 @@ public class PortalCommand implements CommandExecutor {
case "setdest": case "setdest":
if (args.length < 3) { if (args.length < 3) {
p.sendMessage("§cBenutzung: /portal setdest <Name> <Ziel>"); p.sendMessage("§cBenutzung: /portal setdest <Name> <Ziel>");
p.sendMessage("§7Server: ServerName");
p.sendMessage("§7Welt: Weltname;X;Y;Z;Yaw;Pitch");
return true; return true;
} }
String dest = args[2]; String dest = args[2];
// Falls Koordinaten getrennt mit Leerzeichen gegeben werden (z.B. /portal setdest name world 100 64 100)
if (args.length > 3) dest += ";" + args[3]; if (args.length > 3) dest += ";" + args[3];
if (args.length > 4) dest += ";" + args[4]; if (args.length > 4) dest += ";" + args[4];
if (args.length > 5) dest += ";" + args[5]; if (args.length > 5) dest += ";" + args[5];
@@ -96,20 +135,16 @@ public class PortalCommand implements CommandExecutor {
break; break;
case "setspawn": case "setspawn":
// Neuer Befehl: /portal setspawn <Name>
if (args.length < 2) { if (args.length < 2) {
p.sendMessage("§cBenutzung: /portal setspawn <Name>"); p.sendMessage("§cBenutzung: /portal setspawn <Name>");
return true; return true;
} }
// Optional: Berechtigungscheck (anpassbar)
if (!p.hasPermission("nexuslobby.portal")) { if (!p.hasPermission("nexuslobby.portal")) {
p.sendMessage("§cKeine Rechte!"); p.sendMessage("§cKeine Rechte!");
return true; return true;
} }
// Wir speichern die aktuelle Position leicht versetzt (2 Blöcke), damit Spieler nicht direkt wieder im Portal landen.
Location spawnLoc = p.getLocation().clone(); Location spawnLoc = p.getLocation().clone();
spawnLoc.add(0, 0, 2); // einfacher Offset als default spawnLoc.add(0, 0, 2);
if (portalManager.setPortalReturnSpawn(args[1], spawnLoc)) { if (portalManager.setPortalReturnSpawn(args[1], spawnLoc)) {
p.sendMessage("§aPortal-Spawnpunkt für '" + args[1] + "' gesetzt!"); p.sendMessage("§aPortal-Spawnpunkt für '" + args[1] + "' gesetzt!");
@@ -31,7 +31,12 @@ import java.util.UUID;
import java.util.Set; import java.util.Set;
/** /**
* PortalManager - verwaltet Portale, lädt/speichert sie, spawnt Partikel und teleporiert Spieler. * PortalManager - Verwaltet Portale und Wand-Markierungen.
*
* FIX: Die doppelte Border-Logik wurde entfernt. Der BorderModule ist
* der alleinige Verantwortliche für die Lobby-Grenze. Hätte der
* PortalManager die Border ebenfalls geprüft, wären Spieler doppelt
* teleportiert worden und hätten zwei Nachrichten erhalten.
*/ */
public class PortalManager implements Module, Listener { public class PortalManager implements Module, Listener {
@@ -57,7 +62,6 @@ public class PortalManager implements Module, Listener {
loadPortals(); loadPortals();
Bukkit.getPluginManager().registerEvents(this, plugin); Bukkit.getPluginManager().registerEvents(this, plugin);
startParticleTask(); startParticleTask();
plugin.getLogger().info("PortalManager geladen.");
} }
@Override @Override
@@ -70,15 +74,11 @@ public class PortalManager implements Module, Listener {
plugin.getLogger().info("PortalManager deaktiviert."); plugin.getLogger().info("PortalManager deaktiviert.");
} }
/**
* Gibt alle Namen der aktuell geladenen Portale zurück.
* Wird vom LobbyTabCompleter genutzt.
*/
public Set<String> getPortalNames() { public Set<String> getPortalNames() {
return portals.keySet(); return portals.keySet();
} }
// --- Wand / Selection --- // --- Wand / Selection Logic ---
@org.bukkit.event.EventHandler @org.bukkit.event.EventHandler
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() == Action.PHYSICAL) return; if (event.getAction() == Action.PHYSICAL) return;
@@ -87,9 +87,7 @@ public class PortalManager implements Module, Listener {
if (item == null || !item.hasItemMeta()) return; if (item == null || !item.hasItemMeta()) return;
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return; if (meta == null || !meta.getPersistentDataContainer().has(wandKey, PersistentDataType.BYTE)) return;
if (!meta.getPersistentDataContainer().has(wandKey, PersistentDataType.BYTE)) return;
Player p = event.getPlayer(); Player p = event.getPlayer();
if (!p.hasPermission("nexuslobby.portal")) { if (!p.hasPermission("nexuslobby.portal")) {
@@ -98,7 +96,6 @@ public class PortalManager implements Module, Listener {
} }
event.setCancelled(true); event.setCancelled(true);
if (!event.hasBlock()) { if (!event.hasBlock()) {
p.sendMessage("§cDu musst auf einen Block klicken!"); p.sendMessage("§cDu musst auf einen Block klicken!");
return; return;
@@ -113,15 +110,34 @@ public class PortalManager implements Module, Listener {
if (event.getAction() == Action.LEFT_CLICK_BLOCK) { if (event.getAction() == Action.LEFT_CLICK_BLOCK) {
selectionMap.get(uuid)[0] = clickedLoc; selectionMap.get(uuid)[0] = clickedLoc;
PortalCommand.setSelection1(p, clickedLoc);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f);
p.sendMessage("§aPosition 1 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ()); p.sendMessage("§aPosition 1 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ());
} else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { } else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
selectionMap.get(uuid)[1] = clickedLoc; selectionMap.get(uuid)[1] = clickedLoc;
PortalCommand.setSelection2(p, clickedLoc);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.0f); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.0f);
p.sendMessage("§bPosition 2 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ()); p.sendMessage("§bPosition 2 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ());
if (selectionMap.get(uuid)[0] != null) { Location loc1 = selectionMap.get(uuid)[0];
p.sendMessage("§eBenutze jetzt: /portal create <Name> <server|world>"); if (loc1 != null) {
int width = Math.abs(loc1.getBlockX() - clickedLoc.getBlockX()) + 1;
int height = Math.abs(loc1.getBlockY() - clickedLoc.getBlockY()) + 1;
int length = Math.abs(loc1.getBlockZ() - clickedLoc.getBlockZ()) + 1;
long volume = (long) width * height * length;
p.sendMessage("§7§m----------------------------------");
if (volume < 500) {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§eKleiner Bereich erkannt (Portal-Größe)");
p.sendMessage("§fBefehl: §b/portal create <n> <server|world>");
} else {
p.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§6Großer Bereich erkannt (WorldBorder-Größe)");
p.sendMessage("§fBefehl: §a/border square");
}
p.sendMessage("§7§m----------------------------------");
} }
} }
} }
@@ -140,14 +156,8 @@ public class PortalManager implements Module, Listener {
return YamlConfiguration.loadConfiguration(file); return YamlConfiguration.loadConfiguration(file);
} }
/**
* Lädt alle Portale aus der Konfiguration.
* PUBLIC für den Zugriff durch NexusLobby.java beim Reload.
*/
public void loadPortals() { public void loadPortals() {
// Liste leeren, um Duplikate beim Reload zu vermeiden
portals.clear(); portals.clear();
YamlConfiguration portalConfig = loadPortalConfig(); YamlConfiguration portalConfig = loadPortalConfig();
ConfigurationSection section = portalConfig.getConfigurationSection("portals"); ConfigurationSection section = portalConfig.getConfigurationSection("portals");
if (section == null) return; if (section == null) return;
@@ -156,12 +166,9 @@ public class PortalManager implements Module, Listener {
String type = portalConfig.getString("portals." + key + ".type", "WORLD"); String type = portalConfig.getString("portals." + key + ".type", "WORLD");
Portal portal = new Portal(key, type); Portal portal = new Portal(key, type);
if (portalConfig.contains("portals." + key + ".pos1")) { portal.setPos1(loadLocation(portalConfig, "portals." + key + ".pos1"));
portal.setPos1(portalConfig.getLocation("portals." + key + ".pos1")); portal.setPos2(loadLocation(portalConfig, "portals." + key + ".pos2"));
} portal.setReturnSpawn(loadLocation(portalConfig, "portals." + key + ".returnSpawn"));
if (portalConfig.contains("portals." + key + ".pos2")) {
portal.setPos2(portalConfig.getLocation("portals." + key + ".pos2"));
}
portal.setDestination(portalConfig.getString("portals." + key + ".destination", "")); portal.setDestination(portalConfig.getString("portals." + key + ".destination", ""));
try { try {
@@ -170,10 +177,6 @@ public class PortalManager implements Module, Listener {
portal.setParticle(Particle.PORTAL); portal.setParticle(Particle.PORTAL);
} }
if (portalConfig.contains("portals." + key + ".returnSpawn")) {
portal.setReturnSpawn(portalConfig.getLocation("portals." + key + ".returnSpawn"));
}
portals.put(key, portal); portals.put(key, portal);
} }
} }
@@ -185,21 +188,49 @@ public class PortalManager implements Module, Listener {
for (Portal portal : portals.values()) { for (Portal portal : portals.values()) {
String path = "portals." + portal.getName() + "."; String path = "portals." + portal.getName() + ".";
config.set(path + "type", portal.getType()); config.set(path + "type", portal.getType());
config.set(path + "pos1", portal.getPos1()); saveLocation(config, path + "pos1", portal.getPos1());
config.set(path + "pos2", portal.getPos2()); saveLocation(config, path + "pos2", portal.getPos2());
config.set(path + "destination", portal.getDestination()); config.set(path + "destination", portal.getDestination());
config.set(path + "particle", portal.getParticle() != null ? portal.getParticle().name() : "PORTAL"); config.set(path + "particle", portal.getParticle() != null ? portal.getParticle().name() : "PORTAL");
config.set(path + "returnSpawn", portal.getReturnSpawn()); saveLocation(config, path + "returnSpawn", portal.getReturnSpawn());
} }
try { try {
config.save(new java.io.File(plugin.getDataFolder(), "portals.yml")); config.save(new java.io.File(plugin.getDataFolder(), "portals.yml"));
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().severe("Konnte portals.yml nicht speichern!"); plugin.getLogger().severe("Fehler beim Speichern der portals.yml: " + e.getMessage());
e.printStackTrace();
} }
} }
// FIX: Hilfsmethoden für robustes Location-Speichern/Laden.
// Speichert world + x/y/z als einzelne Keys statt als serialisiertes
// Location-Objekt, was nach Neustarts zu null führen kann.
private void saveLocation(YamlConfiguration config, String path, Location loc) {
if (loc == null || loc.getWorld() == null) return;
config.set(path + ".world", loc.getWorld().getName());
config.set(path + ".x", loc.getX());
config.set(path + ".y", loc.getY());
config.set(path + ".z", loc.getZ());
config.set(path + ".yaw", (double) loc.getYaw());
config.set(path + ".pitch", (double) loc.getPitch());
}
private Location loadLocation(YamlConfiguration config, String path) {
if (!config.contains(path + ".world")) return null;
String worldName = config.getString(path + ".world");
World world = Bukkit.getWorld(worldName);
if (world == null) {
plugin.getLogger().warning("Welt '" + worldName + "' nicht gefunden für Pfad: " + path);
return null;
}
return new Location(world,
config.getDouble(path + ".x"),
config.getDouble(path + ".y"),
config.getDouble(path + ".z"),
(float) config.getDouble(path + ".yaw"),
(float) config.getDouble(path + ".pitch"));
}
// --- CRUD --- // --- CRUD ---
public boolean createPortal(String name, String type, Player creator) { public boolean createPortal(String name, String type, Player creator) {
Location[] sel = selectionMap.getOrDefault(creator.getUniqueId(), new Location[2]); Location[] sel = selectionMap.getOrDefault(creator.getUniqueId(), new Location[2]);
@@ -262,31 +293,7 @@ public class PortalManager implements Module, Listener {
return true; return true;
} }
// --- Particles --- // --- Movement / Teleport Logic ---
private void startParticleTask() {
particleTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (Portal portal : portals.values()) {
if (portal.getPos1() == null || portal.getPos2() == null) continue;
spawnParticles(portal);
}
}, 0L, 5L);
}
private void spawnParticles(Portal portal) {
Location min = getMinLocation(portal.getPos1(), portal.getPos2());
Location max = getMaxLocation(portal.getPos1(), portal.getPos2());
World world = portal.getPos1().getWorld();
if (world == null) return;
for (int i = 0; i < 30; i++) {
double x = min.getX() + 0.5 + (Math.random() * (max.getX() - min.getX()));
double y = min.getY() + 0.5 + (Math.random() * (max.getY() - min.getY()));
double z = min.getZ() + 0.5 + (Math.random() * (max.getZ() - min.getZ()));
world.spawnParticle(portal.getParticle(), new Location(world, x, y, z), 3, 0.1, 0.1, 0.1, 0);
}
}
// --- Teleport / Movement ---
@org.bukkit.event.EventHandler @org.bukkit.event.EventHandler
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
if (event.getFrom().getX() == event.getTo().getX() && if (event.getFrom().getX() == event.getTo().getX() &&
@@ -296,8 +303,9 @@ public class PortalManager implements Module, Listener {
} }
Player player = event.getPlayer(); Player player = event.getPlayer();
Location loc = player.getLocation(); Location loc = event.getTo();
// Portal-Logik
for (Portal portal : portals.values()) { for (Portal portal : portals.values()) {
if (portal.getPos1() == null || portal.getPos2() == null) continue; if (portal.getPos1() == null || portal.getPos2() == null) continue;
if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue; if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue;
@@ -317,12 +325,11 @@ public class PortalManager implements Module, Listener {
if ("SERVER".equalsIgnoreCase(portal.getType())) { if ("SERVER".equalsIgnoreCase(portal.getType())) {
String serverName = portal.getDestination(); String serverName = portal.getDestination();
player.sendMessage("§eVerbinde zum Server: " + serverName); player.sendMessage("§eVerbinde zum Server: " + serverName);
plugin.getLogger().info("Verbinde " + player.getName() + " -> " + serverName);
Location loc = portal.getReturnSpawn(); Location loc = portal.getReturnSpawn();
if (loc == null) { if (loc == null) {
Location pos2 = portal.getPos2(); Location pos2 = portal.getPos2();
loc = player.getLocation(); loc = player.getLocation().clone();
if (pos2 != null) { if (pos2 != null) {
double dx = loc.getX() - pos2.getX(); double dx = loc.getX() - pos2.getX();
double dz = loc.getZ() - pos2.getZ(); double dz = loc.getZ() - pos2.getZ();
@@ -335,7 +342,6 @@ public class PortalManager implements Module, Listener {
loc.add(0, 0, 2); loc.add(0, 0, 2);
} }
} }
player.teleport(loc); player.teleport(loc);
connectToServer(player, serverName); connectToServer(player, serverName);
return; return;
@@ -352,8 +358,6 @@ public class PortalManager implements Module, Listener {
if (spawnLoc != null) { if (spawnLoc != null) {
player.teleport(spawnLoc); player.teleport(spawnLoc);
player.sendMessage("§aDu wurdest zum Spawn teleportiert!"); player.sendMessage("§aDu wurdest zum Spawn teleportiert!");
} else {
player.sendMessage("§cSpawn konnte nicht gefunden werden.");
} }
return; return;
} }
@@ -361,99 +365,85 @@ public class PortalManager implements Module, Listener {
String[] parts = dest.split(";"); String[] parts = dest.split(";");
if (parts.length >= 4) { if (parts.length >= 4) {
World world = Bukkit.getWorld(parts[0]); World world = Bukkit.getWorld(parts[0]);
if (world == null) { if (world == null) return;
player.sendMessage("§cZielwelt nicht gefunden: " + parts[0]);
return;
}
try { try {
double x = Double.parseDouble(parts[1]); double x = Double.parseDouble(parts[1]);
double y = Double.parseDouble(parts[2]); double y = Double.parseDouble(parts[2]);
double z = Double.parseDouble(parts[3]); double z = Double.parseDouble(parts[3]);
float yaw = 0f, pitch = 0f; float yaw = parts.length >= 5 ? Float.parseFloat(parts[4]) : 0f;
if (parts.length >= 6) { float pitch = parts.length >= 6 ? Float.parseFloat(parts[5]) : 0f;
yaw = Float.parseFloat(parts[4]); player.teleport(new Location(world, x, y, z, yaw, pitch));
pitch = Float.parseFloat(parts[5]);
}
Location target = new Location(world, x, y, z, yaw, pitch);
player.teleport(target);
player.sendMessage("§aDu wurdest teleportiert!"); player.sendMessage("§aDu wurdest teleportiert!");
} catch (NumberFormatException e) { } catch (NumberFormatException ignored) {}
player.sendMessage("§cUngültige Koordinaten im Portalziel!");
}
} else {
player.sendMessage("§cUngültiges Portalzielformat!");
} }
} }
private void connectToServer(Player player, String serverName) { private void connectToServer(Player player, String serverName) {
try { try {
if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) {
plugin.getLogger().warning("BungeeCord outgoing channel not registered; cannot send plugin message.");
player.sendMessage("§cProxy-Verbindung nicht möglich: BungeeCord-Kanal nicht registriert.");
return;
}
ByteArrayOutputStream b = new ByteArrayOutputStream(); ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(b); DataOutputStream out = new DataOutputStream(b);
out.writeUTF("Connect"); out.writeUTF("Connect");
out.writeUTF(serverName); out.writeUTF(serverName);
player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray());
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().severe("Fehler beim Senden der BungeeCord-Message: " + e.getMessage()); plugin.getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + e.getMessage());
e.printStackTrace();
player.sendMessage("§cFehler beim Verbinden zum Proxy.");
} catch (RuntimeException e) {
plugin.getLogger().warning("Konnte Plugin-Message nicht senden: " + e.getMessage());
player.sendMessage("§cProxy-Verbindung nicht möglich.");
} }
} }
// --- Hilfsfunktionen ---
private Location getMainSpawnLocation() { private Location getMainSpawnLocation() {
String worldName = plugin.getConfig().getString("spawn.world", null); String worldName = plugin.getConfig().getString("spawn.world", null);
if (worldName != null) { if (worldName != null) {
World w = Bukkit.getWorld(worldName); World w = Bukkit.getWorld(worldName);
if (w != null) { if (w != null) {
double x = plugin.getConfig().getDouble("spawn.x", w.getSpawnLocation().getX()); return new Location(w,
double y = plugin.getConfig().getDouble("spawn.y", w.getSpawnLocation().getY()); plugin.getConfig().getDouble("spawn.x"),
double z = plugin.getConfig().getDouble("spawn.z", w.getSpawnLocation().getZ()); plugin.getConfig().getDouble("spawn.y"),
float yaw = (float) plugin.getConfig().getDouble("spawn.yaw", w.getSpawnLocation().getYaw()); plugin.getConfig().getDouble("spawn.z"),
float pitch = (float) plugin.getConfig().getDouble("spawn.pitch", w.getSpawnLocation().getPitch()); (float) plugin.getConfig().getDouble("spawn.yaw"),
return new Location(w, x, y, z, yaw, pitch); (float) plugin.getConfig().getDouble("spawn.pitch"));
} }
} }
if (!Bukkit.getWorlds().isEmpty()) { return !Bukkit.getWorlds().isEmpty() ? Bukkit.getWorlds().get(0).getSpawnLocation() : null;
return Bukkit.getWorlds().get(0).getSpawnLocation();
}
return null;
} }
// --- Utils & Particles ---
private boolean isInArea(Location loc, Location loc1, Location loc2) { private boolean isInArea(Location loc, Location loc1, Location loc2) {
if (loc == null || loc1 == null || loc2 == null) return false;
if (!loc.getWorld().equals(loc1.getWorld())) return false; if (!loc.getWorld().equals(loc1.getWorld())) return false;
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
int headY = y + 1;
Location min = getMinLocation(loc1, loc2); Location min = getMinLocation(loc1, loc2);
Location max = getMaxLocation(loc1, loc2); Location max = getMaxLocation(loc1, loc2);
return (loc.getX() >= min.getX() && loc.getX() <= max.getX() + 1) &&
boolean xMatch = (x >= min.getBlockX()) && (x <= max.getBlockX()); (loc.getY() >= min.getY() && loc.getY() <= max.getY() + 1) &&
boolean zMatch = (z >= min.getBlockZ()) && (z <= max.getBlockZ()); (loc.getZ() >= min.getZ() && loc.getZ() <= max.getZ() + 1);
boolean yMatch = (y >= min.getBlockY()) && (headY <= max.getBlockY());
return xMatch && yMatch && zMatch;
} }
private Location getMinLocation(Location a, Location b) { private Location getMinLocation(Location a, Location b) {
return new Location(a.getWorld(), Math.min(a.getX(), b.getX()), return new Location(a.getWorld(), Math.min(a.getX(), b.getX()), Math.min(a.getY(), b.getY()), Math.min(a.getZ(), b.getZ()));
Math.min(a.getY(), b.getY()), Math.min(a.getZ(), b.getZ()));
} }
private Location getMaxLocation(Location a, Location b) { private Location getMaxLocation(Location a, Location b) {
return new Location(a.getWorld(), Math.max(a.getX(), b.getX()), return new Location(a.getWorld(), Math.max(a.getX(), b.getX()), Math.max(a.getY(), b.getY()), Math.max(a.getZ(), b.getZ()));
Math.max(a.getY(), b.getY()), Math.max(a.getZ(), b.getZ())); }
private void startParticleTask() {
particleTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (Portal portal : portals.values()) {
if (portal.getPos1() != null && portal.getPos2() != null) {
spawnParticles(portal);
}
}
}, 0L, 5L);
}
private void spawnParticles(Portal portal) {
Location min = getMinLocation(portal.getPos1(), portal.getPos2());
Location max = getMaxLocation(portal.getPos1(), portal.getPos2());
World world = portal.getPos1().getWorld();
if (world == null) return;
for (int i = 0; i < 15; i++) {
double x = min.getX() + Math.random() * (max.getX() - min.getX() + 1);
double y = min.getY() + Math.random() * (max.getY() - min.getY() + 1);
double z = min.getZ() + Math.random() * (max.getZ() - min.getZ() + 1);
world.spawnParticle(portal.getParticle(), x, y, z, 1, 0.1, 0.1, 0.1, 0);
}
} }
} }
@@ -115,5 +115,7 @@ public class SecurityModule implements Module, Listener {
} }
@Override @Override
public void onDisable() {} public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
}
} }
@@ -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;
}
}
}
}
@@ -111,7 +111,7 @@ public class ServerSwitcherGUI {
private static void connectToServer(Player player, String serverName, Plugin plugin) { private static void connectToServer(Player player, String serverName, Plugin plugin) {
try { try {
if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) { if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) {
player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)."); player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_not_registered"));
return; return;
} }
@@ -120,13 +120,13 @@ public class ServerSwitcherGUI {
out.writeUTF("Connect"); out.writeUTF("Connect");
out.writeUTF(serverName); out.writeUTF(serverName);
player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray());
player.sendMessage(ChatColor.YELLOW + "Verbinde zu: " + ChatColor.WHITE + serverName); player.sendMessage(de.nexuslobby.utils.LangManager.get("connecting_to_server").replace("{server}", serverName));
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); NexusLobby.getInstance().getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + ex.getMessage());
player.sendMessage(ChatColor.RED + "Fehler beim Verbinden zum Server."); player.sendMessage(de.nexuslobby.utils.LangManager.get("server_connect_error"));
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
// Schutz gegen unerwartete RuntimeExceptions bei plugin messaging // Schutz gegen unerwartete RuntimeExceptions bei plugin messaging
player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich."); player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_connect_error"));
plugin.getLogger().warning("Fehler beim Senden der Plugin-Message: " + ex.getMessage()); plugin.getLogger().warning("Fehler beim Senden der Plugin-Message: " + ex.getMessage());
} }
} }
@@ -4,6 +4,9 @@ import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -19,7 +22,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ServerSwitcherListener implements Listener { public class ServerSwitcherListener implements Listener, CommandExecutor {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent e) { public void onPlayerJoin(PlayerJoinEvent e) {
@@ -51,6 +54,23 @@ public class ServerSwitcherListener implements Listener {
} }
} }
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player"));
return true;
}
Player player = (Player) sender;
if (!player.hasPermission("nexuslobby.serverswitcher")) {
player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission"));
return true;
}
openServerGUI(player);
return true;
}
@EventHandler @EventHandler
public void onCompassClick(PlayerInteractEvent e) { public void onCompassClick(PlayerInteractEvent e) {
Player p = e.getPlayer(); Player p = e.getPlayer();
@@ -126,20 +146,50 @@ public class ServerSwitcherListener implements Listener {
int clickedSlot = e.getSlot(); int clickedSlot = e.getSlot();
ConfigurationSection serversSection = NexusLobby.getInstance().getConfig().getConfigurationSection("compass.servers"); ConfigurationSection serversSection = NexusLobby.getInstance().getConfig().getConfigurationSection("compass.servers");
if (serversSection != null) { if (serversSection == null) return;
for (String serverId : serversSection.getKeys(false)) { for (String serverId : serversSection.getKeys(false)) {
String path = "compass.servers." + serverId; String path = "compass.servers." + serverId;
int configSlot = NexusLobby.getInstance().getConfig().getInt(path + ".slot", -1); int configSlot = NexusLobby.getInstance().getConfig().getInt(path + ".slot", -1);
if (configSlot == clickedSlot) { if (configSlot != clickedSlot) continue;
String command = NexusLobby.getInstance().getConfig().getString(path + ".command");
if (command != null && !command.isEmpty()) { String command = NexusLobby.getInstance().getConfig().getString(path + ".command", "").trim();
if (command.isEmpty()) return;
p.closeInventory(); p.closeInventory();
p.chat("/" + command);
// "server <name>" → BungeeCord Connect Plugin-Message
if (command.toLowerCase().startsWith("server ")) {
String serverName = command.substring(7).trim();
connectBungee(p, serverName);
} else {
// Alle anderen Befehle als Konsolen-Befehl ausführen
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
command.replace("%player%", p.getName()));
}
return; return;
} }
} }
/**
* Sendet den Spieler per BungeeCord Plugin-Message an einen anderen Server.
*/
private void connectBungee(Player player, String serverName) {
if (!Bukkit.getMessenger().isOutgoingChannelRegistered(
NexusLobby.getInstance(), "BungeeCord")) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cKein BungeeCord-Kanal registriert.");
return;
} }
try {
java.io.ByteArrayOutputStream b = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream out = new java.io.DataOutputStream(b);
out.writeUTF("Connect");
out.writeUTF(serverName);
player.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", b.toByteArray());
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§7Verbinde mit §e" + serverName + "§7...");
} catch (java.io.IOException ex) {
player.sendMessage(de.nexuslobby.utils.LangManager.getPrefix() + "§cFehler beim Verbinden: " + ex.getMessage());
} }
} }
} }
@@ -6,12 +6,12 @@ import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.GameRule;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType; // DIESER IMPORT HAT GEFEHLT
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@@ -54,8 +54,31 @@ public class LobbySettingsModule implements Module, Listener {
Set<String> keys = settingsConfig.getConfigurationSection("gamerules").getKeys(false); Set<String> keys = settingsConfig.getConfigurationSection("gamerules").getKeys(false);
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
for (String key : keys) { for (String key : keys) {
String value = String.valueOf(settingsConfig.get("gamerules." + key)); Object value = settingsConfig.get("gamerules." + key);
world.setGameRuleValue(key, value); 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; Material mat = isBool ? ((Boolean)value ? Material.LIME_DYE : Material.GRAY_DYE) : Material.BOOK;
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName("§e" + key); meta.setDisplayName("§e" + key);
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("§7Aktueller Wert: §f" + value); lore.add("§7Aktueller Wert: §f" + value);
@@ -138,13 +162,17 @@ public class LobbySettingsModule implements Module, Listener {
private void handleSubClick(InventoryClickEvent event) { private void handleSubClick(InventoryClickEvent event) {
Player p = (Player) event.getWhoClicked(); Player p = (Player) event.getWhoClicked();
if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) return; ItemStack current = event.getCurrentItem();
if (event.getCurrentItem().getType() == Material.BARRIER) { if (current == null || current.getType() == Material.AIR) return;
if (current.getType() == Material.BARRIER) {
openMainMenu(p); openMainMenu(p);
return; return;
} }
ItemMeta meta = current.getItemMeta();
String key = event.getCurrentItem().getItemMeta().getDisplayName().substring(2); if (meta == null || !meta.hasDisplayName()) return;
String displayName = meta.getDisplayName();
if (displayName.length() < 3) return;
String key = displayName.substring(2);
String path = settingsConfig.contains("gamerules." + key) ? "gamerules." + key : key; String path = settingsConfig.contains("gamerules." + key) ? "gamerules." + key : key;
Object value = settingsConfig.get(path); Object value = settingsConfig.get(path);
String category = event.getView().getTitle().replace(subTitlePrefix, ""); String category = event.getView().getTitle().replace(subTitlePrefix, "");
@@ -154,7 +182,7 @@ public class LobbySettingsModule implements Module, Listener {
settingsConfig.set(path, newValue); settingsConfig.set(path, newValue);
saveSettings(); saveSettings();
if (path.startsWith("gamerules.")) { 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) { } else if (value instanceof Integer) {
int newVal = (Integer) value + (event.getClick().isLeftClick() ? 1 : -1); int newVal = (Integer) value + (event.getClick().isLeftClick() ? 1 : -1);
@@ -162,7 +190,7 @@ public class LobbySettingsModule implements Module, Listener {
settingsConfig.set(path, newVal); settingsConfig.set(path, newVal);
saveSettings(); saveSettings();
if (path.startsWith("gamerules.")) { 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) { 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"); 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("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"); 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() { private void saveSettings() {
try { settingsConfig.save(configFile); } catch (IOException e) { e.printStackTrace(); } try {
settingsConfig.save(configFile);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der settings.yml: " + e.getMessage());
}
} }
private ItemStack createItem(Material mat, String name, String lore) { private ItemStack createItem(Material mat, String name, String lore) {
ItemStack item = new ItemStack(mat); ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
meta.setDisplayName(name); meta.setDisplayName(name);
if (!lore.isEmpty()) { if (!lore.isEmpty()) {
List<String> lores = new ArrayList<>(); List<String> lores = new ArrayList<>();
@@ -199,5 +231,8 @@ public class LobbySettingsModule implements Module, Listener {
} }
@Override @Override
public void onDisable() { saveSettings(); } public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
saveSettings();
}
} }
@@ -47,6 +47,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List
@Override @Override
public void onDisable() { public void onDisable() {
org.bukkit.event.HandlerList.unregisterAll(this);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CONTROL); plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CONTROL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CHAT); plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_CHAT);
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_CONTROL); plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_CONTROL);
@@ -84,7 +85,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List
} }
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Bukkit.getLogger().severe("Fehler beim Abrufen des Conversation-Managers: " + e.getMessage());
} }
} }
@@ -1,210 +1,311 @@
package de.nexuslobby.modules.tablist; package de.nexuslobby.modules.tablist;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module; import de.nexuslobby.api.Module;
import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User; import net.luckperms.api.model.user.User;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team; import org.bukkit.scoreboard.Team;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
public class TablistModule implements Module { public class TablistModule implements Module {
private BukkitTask refreshTask; private BukkitTask refreshTask;
private int currentHeaderIndex = 0; private int currentHeaderIndex = 0;
private int currentFooterIndex = 0; private int currentFooterIndex = 0;
private LuckPerms luckPerms; private LuckPerms luckPerms;
private boolean placeholderAPIEnabled; private boolean placeholderAPIEnabled;
// Pixel-Breiten Tabelle für die Standard Minecraft Schriftart
private static final Map<Character, Integer> CHAR_WIDTHS = new HashMap<>();
private static final int TARGET_PIXEL_WIDTH = 90; // Ziel-Breite für den Namensteil vor dem Ping
static {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (char c : chars.toCharArray()) CHAR_WIDTHS.put(c, 6);
CHAR_WIDTHS.put('i', 2); CHAR_WIDTHS.put('l', 3); CHAR_WIDTHS.put('t', 4);
CHAR_WIDTHS.put('I', 4); CHAR_WIDTHS.put('f', 5); CHAR_WIDTHS.put('k', 5);
CHAR_WIDTHS.put('.', 2); CHAR_WIDTHS.put(',', 2); CHAR_WIDTHS.put('!', 2);
CHAR_WIDTHS.put('(', 5); CHAR_WIDTHS.put(')', 5); CHAR_WIDTHS.put('[', 4);
CHAR_WIDTHS.put(']', 4); CHAR_WIDTHS.put('{', 5); CHAR_WIDTHS.put('}', 5);
CHAR_WIDTHS.put('|', 2); CHAR_WIDTHS.put('*', 5); CHAR_WIDTHS.put(' ', 4);
CHAR_WIDTHS.put('_', 6);
}
@Override @Override
public String getName() { return "Tablist"; } public String getName() { return "Tablist"; }
@Override @Override
public void onEnable() { public void onEnable() {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
if (!vConfig.getBoolean("tablist.enabled", true)) return; if (!vConfig.getBoolean("tablist.enabled", true)) return;
if (Bukkit.getPluginManager().getPlugin("LuckPerms") != null) luckPerms = LuckPermsProvider.get(); if (Bukkit.getPluginManager().getPlugin("LuckPerms") != null) luckPerms = LuckPermsProvider.get();
placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null;
// Nutzt jetzt das Intervall aus der visuals.yml
long interval = vConfig.getLong("tablist.interval-ticks", 40L); long interval = vConfig.getLong("tablist.interval-ticks", 40L);
refreshTask = Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::refreshAll, 10L, interval); refreshTask = Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::refreshAll, 10L, interval);
} }
private void refreshAll() { private void refreshAll() {
updateAnimationIndices(); updateAnimationIndices();
for (Player viewer : Bukkit.getOnlinePlayers()) { for (Player viewer : Bukkit.getOnlinePlayers()) {
viewer.setPlayerListHeader(getHeader(viewer)); viewer.setPlayerListHeader(getHeader(viewer));
viewer.setPlayerListFooter(getFooter(viewer)); viewer.setPlayerListFooter(getFooter(viewer));
updateTeamsAndFormatting(viewer);
} updateTeams(viewer);
} }
private void updateTeamsAndFormatting(Player viewer) { }
private void updateTeams(Player viewer) {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
boolean showPrefix = vConfig.getBoolean("tablist.show-prefix-in-playerlist", true);
if (!vConfig.getBoolean("tablist.show-prefix-in-playerlist", true)) return;
Scoreboard sb = viewer.getScoreboard(); Scoreboard sb = viewer.getScoreboard();
if (sb == Bukkit.getScoreboardManager().getMainScoreboard()) return; if (sb == Bukkit.getScoreboardManager().getMainScoreboard()) return;
for (Player target : Bukkit.getOnlinePlayers()) { for (Player target : Bukkit.getOnlinePlayers()) {
String sortPriority = getSortPriority(target);
String teamName = sortPriority + target.getName(); String teamName = "tab_" + target.getName();
if (teamName.length() > 16) teamName = teamName.substring(0, 16); if (teamName.length() > 16) teamName = teamName.substring(0, 16);
Team team = sb.getTeam(teamName); Team team = sb.getTeam(teamName);
if (team == null) team = sb.registerNewTeam(teamName); if (team == null) team = sb.registerNewTeam(teamName);
String prefix = showPrefix ? getPlayerPrefix(target) : "§7";
String prefix = getPlayerPrefix(target);
String suffix = getPlayerSuffix(target);
if (!team.getPrefix().equals(prefix)) team.setPrefix(prefix); if (!team.getPrefix().equals(prefix)) team.setPrefix(prefix);
// Hier wird der Pixel-Ausgleich berechnet, damit der Trenner | immer an der gleichen Stelle ist if (!team.getSuffix().equals(suffix)) team.setSuffix(suffix);
String pingSuffix = getAlignedPingSuffix(target.getName(), target.getPing());
if (!team.getSuffix().equals(pingSuffix)) team.setSuffix(pingSuffix);
if (!team.hasEntry(target.getName())) { if (!team.hasEntry(target.getName())) {
team.addEntry(target.getName()); team.addEntry(target.getName());
} }
// Setzt den Listnamen zurück, damit Team-Prefix/Suffix greifen
if (target.getPlayerListName() != null) {
target.setPlayerListName(null);
}
}
} }
private String getAlignedPingSuffix(String name, int ping) {
int currentWidth = 0;
for (char c : name.toCharArray()) {
currentWidth += CHAR_WIDTHS.getOrDefault(c, 6);
} }
int diff = TARGET_PIXEL_WIDTH - currentWidth;
StringBuilder spacer = new StringBuilder(" ");
// Ausgleich mit fetten (5px) und normalen (4px) Leerzeichen für maximale Präzision
while (diff > 0) {
if (diff >= 5) {
spacer.append("§l ");
diff -= 5;
} else {
spacer.append(" ");
diff -= 4;
}
}
String pingColor = (ping < 50) ? "§a" : (ping < 100 ? "§e" : "§c");
// §r bricht das fettgedruckte Leerzeichen ab, damit der Ping normal aussieht
return "§r" + spacer.toString() + "§8| " + pingColor + ping + "ms";
}
private String getSortPriority(Player player) {
if (luckPerms == null) return "z_";
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) return "z_";
Group group = luckPerms.getGroupManager().getGroup(user.getPrimaryGroup());
if (group == null) return "z_";
int weight = group.getWeight().orElse(0);
int invertedWeight = 1000 - weight;
return String.format("%03d", invertedWeight) + "_";
}
private String getPlayerPrefix(Player player) { private String getPlayerPrefix(Player player) {
String prefix = ""; String prefix = "";
if (luckPerms != null) { if (luckPerms != null) {
User user = luckPerms.getUserManager().getUser(player.getUniqueId()); User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user != null && user.getCachedData().getMetaData().getPrefix() != null) { if (user != null && user.getCachedData().getMetaData().getPrefix() != null) {
prefix = user.getCachedData().getMetaData().getPrefix(); prefix = user.getCachedData().getMetaData().getPrefix();
} }
} }
if (prefix.isEmpty() && placeholderAPIEnabled) { if (prefix.isEmpty() && placeholderAPIEnabled) {
prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%");
} }
return colorize(prefix.isEmpty() ? "&7" : prefix + " "); return colorize(prefix.isEmpty() ? "&7" : prefix + " ");
} }
private String getPlayerSuffix(Player player) {
// Hier könnte man später auch Suffixe aus der Config laden
return colorize(" &8[&a" + player.getPing() + "ms&8]");
}
private void updateAnimationIndices() { private void updateAnimationIndices() {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
List<String> h = vConfig.getStringList("tablist.header-animations"); List<String> h = vConfig.getStringList("tablist.header-animations");
List<String> f = vConfig.getStringList("tablist.footer-animations"); List<String> f = vConfig.getStringList("tablist.footer-animations");
if (!h.isEmpty()) currentHeaderIndex = (currentHeaderIndex + 1) % h.size(); if (!h.isEmpty()) currentHeaderIndex = (currentHeaderIndex + 1) % h.size();
if (!f.isEmpty()) currentFooterIndex = (currentFooterIndex + 1) % f.size(); if (!f.isEmpty()) currentFooterIndex = (currentFooterIndex + 1) % f.size();
} }
private String getHeader(Player p) { private String getHeader(Player p) {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
List<String> list = vConfig.getStringList("tablist.header-animations"); List<String> list = vConfig.getStringList("tablist.header-animations");
if (list.isEmpty()) return "§6NexusLobby"; if (list.isEmpty()) return "§6NexusLobby";
return replacePlaceholders(list.get(currentHeaderIndex), p); return replacePlaceholders(list.get(currentHeaderIndex), p);
} }
private String getFooter(Player p) { private String getFooter(Player p) {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
List<String> list = vConfig.getStringList("tablist.footer-animations"); List<String> list = vConfig.getStringList("tablist.footer-animations");
if (list.isEmpty()) return "§7Willkommen"; if (list.isEmpty()) return "§7Willkommen";
return replacePlaceholders(list.get(currentFooterIndex), p); return replacePlaceholders(list.get(currentFooterIndex), p);
} }
private String replacePlaceholders(String text, Player p) { private String replacePlaceholders(String text, Player p) {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
text = text.replace("{server}", vConfig.getString("tablist.server-name", "NexusLobby")); text = text.replace("{server}", vConfig.getString("tablist.server-name", "NexusLobby"));
text = text.replace("{player}", p.getName()); text = text.replace("{player}", p.getName());
text = text.replace("{online}", String.valueOf(Bukkit.getOnlinePlayers().size())); text = text.replace("{online}", String.valueOf(Bukkit.getOnlinePlayers().size()));
text = text.replace("{staff}", String.valueOf(getOnlineStaffCount())); text = text.replace("{staff}", String.valueOf(getOnlineStaffCount()));
text = text.replace("{separator}", vConfig.getString("tablist.separator-line", "")); text = text.replace("{separator}", vConfig.getString("tablist.separator-line", ""));
text = text.replace("{website}", vConfig.getBoolean("tablist.show-website") ? vConfig.getString("tablist.website", "") : ""); text = text.replace("{website}", vConfig.getBoolean("tablist.show-website") ? vConfig.getString("tablist.website", "") : "");
text = text.replace("{teamspeak}", vConfig.getBoolean("tablist.show-teamspeak") ? vConfig.getString("tablist.teamspeak-address", "") : ""); text = text.replace("{teamspeak}", vConfig.getBoolean("tablist.show-teamspeak") ? vConfig.getString("tablist.teamspeak-address", "") : "");
text = text.replace("{discord}", vConfig.getBoolean("tablist.show-discord") ? vConfig.getString("tablist.discord-address", "") : ""); text = text.replace("{discord}", vConfig.getBoolean("tablist.show-discord") ? vConfig.getString("tablist.discord-address", "") : "");
if (placeholderAPIEnabled) { if (placeholderAPIEnabled) {
text = PlaceholderAPI.setPlaceholders(p, text); text = PlaceholderAPI.setPlaceholders(p, text);
} }
return colorize(text); return colorize(text);
} }
private int getOnlineStaffCount() { private int getOnlineStaffCount() {
FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig(); FileConfiguration vConfig = NexusLobby.getInstance().getVisualsConfig();
String permission = vConfig.getString("tablist.staff-permission", "nexuslobby.staff"); String permission = vConfig.getString("tablist.staff-permission", "nexuslobby.staff");
int count = 0; int count = 0;
for (Player p : Bukkit.getOnlinePlayers()) { for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission(permission)) count++; if (p.hasPermission(permission)) count++;
} }
return count; return count;
} }
private String colorize(String s) { private String colorize(String s) {
return s == null ? "" : s.replace("&", "§"); return s == null ? "" : s.replace("&", "§");
} }
@Override @Override
public void onDisable() { public void onDisable() {
if (refreshTask != null) refreshTask.cancel(); if (refreshTask != null) refreshTask.cancel();
} }
} }
@@ -1,6 +1,7 @@
package de.nexuslobby.utils; package de.nexuslobby.utils;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@@ -23,7 +24,13 @@ public class ConfigUpdater {
FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile); FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile);
List<String> newFileContent = new ArrayList<>(); List<String> newFileContent = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(plugin.getResource(fileName), StandardCharsets.UTF_8))) { InputStream resourceStream = plugin.getResource(fileName);
if (resourceStream == null) {
plugin.getLogger().severe("Config-Update fehlgeschlagen: Resource fehlt: " + fileName);
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
String trimmed = line.trim(); String trimmed = line.trim();
@@ -52,7 +59,7 @@ public class ConfigUpdater {
} }
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Bukkit.getLogger().severe("Fehler beim Config-Update: " + e.getMessage());
} }
// 3. Datei sauber speichern // 3. Datei sauber speichern
@@ -0,0 +1,156 @@
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() {
ensureDefault("prefix", "&8[&6Nexus&8] ");
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");
}
}
@@ -41,7 +41,7 @@ public class DoubleJump implements Listener {
@EventHandler @EventHandler
public void onMove(PlayerMoveEvent event) { public void onMove(PlayerMoveEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (player.getGameMode() != GameMode.CREATIVE && player.getLocation().subtract(0, 0.1, 0).getBlock().getType() != Material.AIR) { if (player.getGameMode() != GameMode.CREATIVE && player.getGameMode() != GameMode.SPECTATOR && player.getLocation().subtract(0, 0.1, 0).getBlock().getType() != Material.AIR) {
if (NexusLobby.getInstance().getConfig().getBoolean("doublejump.enabled", true)) { if (NexusLobby.getInstance().getConfig().getBoolean("doublejump.enabled", true)) {
player.setAllowFlight(true); player.setAllowFlight(true);
} }
@@ -0,0 +1,43 @@
package de.nexuslobby.utils;
import de.nexuslobby.NexusLobby;
import org.bukkit.ChatColor;
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)
.replace("§8[§6Nexus§8] ", getPrefix());
}
public static String getPrefix() {
NexusLobby plugin = NexusLobby.getInstance();
String prefix = plugin == null
? "&8[&6Nexus&8] "
: plugin.getConfig().getString("prefix", "&8[&6Nexus&8] ");
return ChatColor.translateAlternateColorCodes('&', prefix);
}
}
@@ -9,6 +9,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
@@ -24,13 +25,22 @@ public class PlayerHider implements Listener {
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return; if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return;
Material material = Material.valueOf(NexusLobby.getInstance().getConfig().getString("hider.item", "REDSTONE")); var config = NexusLobby.getInstance().getConfig();
int slot = NexusLobby.getInstance().getConfig().getInt("hider.slot", 8); Material material;
try {
material = Material.valueOf(config.getString("hider.item", "REDSTONE"));
} catch (IllegalArgumentException e) {
material = Material.REDSTONE;
}
int slot = config.getInt("hider.slot", 8);
ItemStack item = new ItemStack(material); ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
meta.setDisplayName(NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§")); if (meta != null) {
String allMsg = colorize(config.getString("hider.messages.all", "&aAlle Spieler"));
meta.setDisplayName(allMsg);
item.setItemMeta(meta); item.setItemMeta(meta);
}
event.getPlayer().getInventory().setItem(slot, item); event.getPlayer().getInventory().setItem(slot, item);
} }
@@ -39,32 +49,45 @@ public class PlayerHider implements Listener {
public void onInteract(PlayerInteractEvent event) { public void onInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.getItem() == null) return; if (event.getItem() == null) return;
if (!event.getItem().hasItemMeta()) return;
if (event.getItem().getItemMeta() == null) return;
if (!event.getItem().getItemMeta().hasDisplayName()) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
String allMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§"); var config = NexusLobby.getInstance().getConfig();
String noneMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.none").replace("&", "§"); String allMsg = de.nexuslobby.utils.LangManager.get("hider_all");
String noneMsg = de.nexuslobby.utils.LangManager.get("hider_none");
if (event.getItem().getItemMeta().getDisplayName().equals(allMsg)) { if (event.getItem().getItemMeta().getDisplayName().equals(allMsg)) {
// Verstecken // Verstecken
hidden.add(player.getUniqueId()); hidden.add(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target); for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target);
updateItem(player, noneMsg); updateItem(player, noneMsg);
player.sendMessage(noneMsg); player.sendMessage(noneMsg);
} else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) { } else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) {
// Zeigen // Zeigen
hidden.remove(player.getUniqueId()); hidden.remove(player.getUniqueId());
for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target); for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target);
updateItem(player, allMsg); updateItem(player, allMsg);
player.sendMessage(allMsg); player.sendMessage(allMsg);
} }
} }
private void updateItem(Player player, String name) { private void updateItem(Player player, String name) {
ItemStack item = player.getInventory().getItemInHand(); ItemStack item = player.getInventory().getItemInMainHand();
if (item == null || !item.hasItemMeta()) return;
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.setDisplayName(name); meta.setDisplayName(name);
item.setItemMeta(meta); item.setItemMeta(meta);
} }
private String colorize(String s) {
return s == null ? "" : s.replace("&", "§");
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
hidden.remove(event.getPlayer().getUniqueId());
}
} }
@@ -2,6 +2,7 @@ package de.nexuslobby.utils;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
@@ -11,8 +12,7 @@ import java.util.function.Consumer;
public class UpdateChecker { public class UpdateChecker {
private final NexusLobby plugin; private final NexusLobby plugin;
// URL zur Gitea API für das neueste Release private static final int RESOURCE_ID = 132388;
private final String url = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest";
public UpdateChecker(NexusLobby plugin) { public UpdateChecker(NexusLobby plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -20,22 +20,16 @@ public class UpdateChecker {
public void getVersion(final Consumer<String> consumer) { public void getVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream inputStream = new URL(url).openStream(); Scanner scanner = new Scanner(inputStream)) { try (InputStream is = new URL(
StringBuilder response = new StringBuilder(); "https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID + "/~").openStream();
while (scanner.hasNextLine()) { Scanner scanner = new Scanner(is)) {
response.append(scanner.nextLine());
if (scanner.hasNext()) {
consumer.accept(scanner.next());
} }
String content = response.toString(); } catch (IOException e) {
// Einfaches Parsing des JSON "tag_name" Feldes plugin.getLogger().warning("Update-Check fehlgeschlagen: " + e.getMessage());
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);
}
} catch (IOException exception) {
this.plugin.getLogger().warning("Update-Check fehlgeschlagen: " + exception.getMessage());
} }
}); });
} }
@@ -3,6 +3,8 @@ package de.nexuslobby.utils;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -17,11 +19,25 @@ public class VoidProtection implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (player.getLocation().getY() < 0) { if (player.getLocation().getY() < 0) {
if (NexusLobby.getInstance().getConfig().getBoolean("void_protection.teleport_to_spawn", true)) { if (NexusLobby.getInstance().getConfig().getBoolean("void_protection.teleport_to_spawn", true)) {
Location spawn = player.getWorld().getSpawnLocation(); FileConfiguration config = NexusLobby.getInstance().getConfig();
Location spawn;
if (config.contains("spawn.world")) {
String worldName = config.getString("spawn.world");
World world = Bukkit.getWorld(worldName);
if (world != null) {
spawn = new Location(world,
config.getDouble("spawn.x"), config.getDouble("spawn.y"), config.getDouble("spawn.z"),
(float) config.getDouble("spawn.yaw"), (float) config.getDouble("spawn.pitch"));
} else {
spawn = player.getWorld().getSpawnLocation();
}
} else {
spawn = player.getWorld().getSpawnLocation();
}
player.teleport(spawn); player.teleport(spawn);
String msg = NexusLobby.getInstance().getConfig().getString("void_protection.message"); String msg = de.nexuslobby.utils.LangManager.get("void_protection_message");
if (msg != null && !msg.isEmpty()) { if (msg != null && !msg.isEmpty()) {
player.sendMessage(msg.replace("&", "§")); player.sendMessage(msg);
} }
} }
} }
+9 -1
View File
@@ -1,5 +1,13 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# -----------------------------------------------------
# ArmorStandTools Configuration # ArmorStandTools Configuration
# ----------------------------- # -----------------------------------------------------
# Nachrichten # Nachrichten
prefix: "§8[§6ArmorStand§8] §7" prefix: "§8[§6ArmorStand§8] §7"
+432 -80
View File
@@ -1,68 +1,255 @@
# ========================== # _ __ __ __ __
# NexusLobby Konfiguration # / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# ========================== # / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# =============================================================
# NEXUSLOBBY - HAUPTKONFIGURATION
# =============================================================
#
# Diese Konfigurationsdatei steuert alle Aspekte deiner Lobby.
# Farben werden mit & formatiert (z.B. &a = grün, &c = rot, &e = gelb)
# Vollständige Farbcode-Liste: https://minecraft.fandom.com/wiki/Formatting_codes
# --- Spawn Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
# SPRACHE
# ══════════════════════════════════════════════════════════════════════════════
# Spracheinstellung für alle Texte und Nachrichten im Plugin
# Verfügbare Optionen: de (Deutsch), en (Englisch), fr (Französisch)
language: de
# Prefix vor Plugin-Nachrichten im Chat (&-Farbcodes sind erlaubt)
prefix: "&8[&6Nexus&8] "
# ══════════════════════════════════════════════════════════════════════════════
# SPAWN EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Definiert den Spawn-Punkt der Lobby, zu dem Spieler beim Join teleportiert werden
# Tipp: Nutze den Befehl /nexus setspawn um diese Werte automatisch zu setzen
spawn: spawn:
world: "world" # Name der Standardwelt # Name der Welt, in der sich der Spawn befindet
x: 0.5 # X-Koordinate des Spawns world: "world"
y: 64.0 # Y-Koordinate des Spawns
z: 0.5 # Z-Koordinate des Spawns
yaw: 0.0 # Blickrichtung
pitch: 0.0 # Blickrichtung
# --- Lobby Einstellungen --- # X-Koordinate des Spawn-Punktes (Ost-West)
x: 0.5
# Y-Koordinate des Spawn-Punktes (Höhe)
y: 64.0
# Z-Koordinate des Spawn-Punktes (Nord-Süd)
z: 0.5
# Horizontale Blickrichtung beim Spawn (0-360 Grad)
# 0 = Süden, 90 = Westen, 180 = Norden, 270 = Osten
yaw: 0.0
# Vertikale Blickrichtung beim Spawn (-90 bis 90 Grad)
# -90 = Direkt nach oben, 0 = Geradeaus, 90 = Direkt nach unten
pitch: 0.0
# ══════════════════════════════════════════════════════════════════════════════
# WELTGRENZE (WORLD BORDER)
# ══════════════════════════════════════════════════════════════════════════════
# Erstellt eine unsichtbare Barriere um die Lobby herum
worldborder:
# Aktiviert die Weltgrenze (true = an, false = aus)
enabled: true
# Form der Weltgrenze
# Optionen: "SQUARE" (Quadrat), "CIRCLE" (Kreis)
type: "SQUARE"
# Radius der Weltgrenze in Blöcken
# Bei SQUARE: Kantenlänge = radius * 2
# Bei CIRCLE: Durchmesser = radius * 2
radius: 50.0
# Zentrum der Weltgrenze (optional, kann leer bleiben)
# Wenn nicht gesetzt, wird der Spawn-Punkt als Zentrum verwendet
# center wird automatisch über /border circle gesetzt
# Alternative: Definiere Eckpunkte (für rechteckige Border)
# Format: x,y,z
# pos1 wird automatisch über /border square gesetzt
# pos2 wird automatisch über /border square gesetzt
# ══════════════════════════════════════════════════════════════════════════════
# LOBBY EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Grundlegende Verhaltensregeln und Einstellungen für die Lobby
lobby: lobby:
allow-fly: false # Spieler dürfen fliegen # Erlaubt Spielern das Fliegen in der Lobby
pvp-enabled: false # PvP in der Lobby # true = Spieler können fliegen, false = Fliegen ist deaktiviert
build-enabled: false # Bau im Lobby-Bereich allow-fly: false
# Aktiviert PvP (Player vs Player) in der Lobby
# true = Spieler können sich gegenseitig angreifen, false = kein PvP
pvp-enabled: false
# Erlaubt Spielern das Platzieren und Abbauen von Blöcken
# true = Bauen erlaubt, false = Bauen verboten (außer für Berechtigte)
build-enabled: false
# Standard-Spielmodus für alle Spieler in der Lobby
# Optionen: Survival, Creative, Adventure, Spectator
default-gamemode: Adventure default-gamemode: Adventure
# Leert das Inventar von Spielern beim Betreten der Lobby
# true = Inventar wird geleert, false = Inventar bleibt erhalten
# Empfohlen: true für ein sauberes Lobby-Erlebnis
clear-inventory-on-join: true clear-inventory-on-join: true
# --- Tablist Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
tablist: # SCOREBOARD EINSTELLUNGEN
enabled: true # ══════════════════════════════════════════════════════════════════════════════
header: "&6Willkommen auf &eNexusLobby" # Steuert das Scoreboard (rechte Seitenleiste) in der Lobby
footer: "&7Viel Spaß!" # Standardmäßig sichtbar? Spieler können es per Befehl ein-/ausblenden
refresh-interval: 40 # Ticks # true = Scoreboard wird beim Join angezeigt, false = standardmäßig ausgeblendet
scoreboard-default-visible: true
# --- Items Modul Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
items: # SERVER-STATUS PING (FÜR ARMORSTANDS)
lobby-tools: # ══════════════════════════════════════════════════════════════════════════════
compass: # Mapping für den Server-Status-Ping der ArmorStands in der Lobby
enabled: true # Diese Einstellungen ermöglichen es, den Status anderer Server zu überprüfen
displayname: "&eTeleporter" # WICHTIG: Der Name (z.B. "survival") muss EXAKT dem BungeeCord-Servernamen entsprechen
slot: 4
build-toggle:
enabled: true
displayname: "&aBaumodus"
slot: 1
gadget:
enabled: true
displayname: "&bGadgets"
slot: 2
# --- Portal Einstellungen ---
portals:
default-particle: "PORTAL"
portal-cooldown: 40 # Ticks, 2 Sekunden
save-file: "portals.yml" # Datei im Plugin-Ordner
# -----------------------------------------------------
# COMPASS MENU
# -----------------------------------------------------
compass:
title: "&eServer Switcher"
size: 27
servers: servers:
# Erster Server: Survival
survival:
# IP-Adresse des Survival-Servers
# 127.0.0.1 = Localhost (Server läuft auf derselben Maschine)
ip: "127.0.0.1"
# Port des Survival-Servers
port: 25566
# Zweiter Server: Skyblock
skyblock:
# IP-Adresse des Skyblock-Servers
ip: "127.0.0.1"
# Port des Skyblock-Servers
port: 25567
# ══════════════════════════════════════════════════════════════════════════════
# TABLISTE (TAB-LISTE) EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Anpassung der Player-Liste (TAB-Taste)
tablist:
# Aktiviert die angepasste Tabliste
# true = Custom Tablist wird verwendet, false = Standard Minecraft Tablist
enabled: true
# Kopfzeile über den Spielernamen
# Unterstützt Farbcodes (&-Codes) und mehrzeiligen Text mit |
header: "&6Willkommen auf &eNexusLobby"
# Fußzeile unter den Spielernamen
# Unterstützt Farbcodes (&-Codes) und mehrzeiligen Text mit |
footer: "&7Viel Spaß!"
# Aktualisierungsintervall in Ticks (20 Ticks = 1 Sekunde)
# Niedrigere Werte = häufigere Updates (mehr Performance-Last)
# Höhere Werte = seltenere Updates (weniger Performance-Last)
refresh-interval: 40
# ══════════════════════════════════════════════════════════════════════════════
# ITEMS MODUL EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration der Items, die Spieler in der Lobby erhalten
items:
# Container für alle Lobby-Werkzeuge
lobby-tools:
# Kompass (Server-Auswahl / Teleporter)
compass:
# Aktiviert das Kompass-Item (true = an, false = aus)
enabled: false
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&eTeleporter"
# Slot im Inventar (0-8, wobei 0 ganz links ist und 8 ganz rechts)
slot: 4
# Baumodus-Umschalter (nur für berechtigte Spieler)
build-toggle:
# Aktiviert das Baumodus-Item (true = an, false = aus)
enabled: true
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&aBaumodus"
# Slot im Inventar (0-8)
slot: 0
# Gadget-Menü (Spezialeffekte und Extras)
gadget:
# Aktiviert das Gadget-Item (true = an, false = aus)
enabled: true
# Anzeigename des Items (unterstützt Farbcodes)
displayname: "&bGadgets"
# Slot im Inventar (0-8)
slot: 8
# ══════════════════════════════════════════════════════════════════════════════
# PORTAL EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration für Teleportations-Portale in der Lobby
portals:
# Standard-Partikeleffekt für alle Portale
# Optionen: PORTAL, FLAME, VILLAGER_HAPPY, REDSTONE, ENCHANTMENT_TABLE, etc.
# Vollständige Liste: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html
default-particle: "PORTAL"
# Cooldown zwischen Portal-Nutzungen in Ticks (20 Ticks = 1 Sekunde)
# Verhindert Spam und ungewollte Mehrfach-Teleports
portal-cooldown: 40
# Dateiname für die Speicherung der Portal-Positionen
# Wird automatisch im Plugin-Ordner erstellt
save-file: "portals.yml"
# ══════════════════════════════════════════════════════════════════════════════
# COMPASS MENU (SERVER SWITCHER)
# ══════════════════════════════════════════════════════════════════════════════
# GUI-Menü das sich öffnet, wenn ein Spieler auf den Kompass klickt
compass:
# Titel des Inventar-Menüs (unterstützt Farbcodes)
title: "&eServer Switcher"
# Größe des Inventars (muss ein Vielfaches von 9 sein)
# Optionen: 9, 18, 27, 36, 45, 54
size: 27
# Server-Einträge im Menü
servers:
# Erster Server: PvP
pvp: pvp:
# Anzeigename des Items im Menü (unterstützt Farbcodes)
name: "&cPvP Arena" name: "&cPvP Arena"
# Material des Items (muss ein gültiger Minecraft-Material-Name sein)
# Liste: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html
material: "DIAMOND_SWORD" material: "DIAMOND_SWORD"
# Befehl der beim Klick ausgeführt wird
# Für BungeeCord: "server <servername>"
# Für Teleport: "spawn <name>" oder andere Custom-Befehle
command: "server pvp" command: "server pvp"
# Position des Items im Inventar (0 = oben links)
slot: 11 slot: 11
# Beschreibung des Items (wird beim Darüberfahren angezeigt)
lore: lore:
- "&7Klicke hier um dich" - "&7Klicke hier um dich"
- "&7zum PvP Server zu teleportieren." - "&7zum PvP Server zu teleportieren."
# Zweiter Server: Survival
survival: survival:
name: "&aSurvival" name: "&aSurvival"
material: "GRASS_BLOCK" material: "GRASS_BLOCK"
@@ -71,6 +258,8 @@ compass:
lore: lore:
- "&7Das normale Survival." - "&7Das normale Survival."
- "&7Viel Spaß beim Bauen!" - "&7Viel Spaß beim Bauen!"
# Dritter Server: BuildBattle
buildbattle: buildbattle:
name: "&bBuildBattle" name: "&bBuildBattle"
material: "BEDROCK" material: "BEDROCK"
@@ -79,59 +268,222 @@ compass:
lore: lore:
- "&7Zeige was du kannst!" - "&7Zeige was du kannst!"
# --- Suppressor / Global Chat Einstellungen --- # ══════════════════════════════════════════════════════════════════════════════
suppressor: # PLAYER INSPECT (STATISTIKEN PER RECHTSKLICK)
# ══════════════════════════════════════════════════════════════════════════════
# Ermöglicht das Anzeigen von Spieler-Statistiken durch Rechtsklick auf einen Spieler
player_inspect:
# Aktiviert die Player-Inspect-Funktion (true = an, false = aus)
enabled: true enabled: true
suppress-join-quit: true
suppress-duration-ticks: 40 # Zeit, bis Spieler wieder sichtbar
channels:
control: "global:control" # Channel für Join/Quit Suppression
chat: "global:chat" # Channel für globales Chat-Relay
# --- Logging Einstellungen --- # Titel des GUI-Fensters das sich öffnet
logging: # Platzhalter: {PLAYER} = Name des angeklickten Spielers
enable-debug: true # Aktiviert detaillierte Logs für Module gui_title: "&8Statistiken von &6{PLAYER}"
log-file: "logs/plugin.log" # Pfad für das Logfile
# --- Wartungsmodus --- # ══════════════════════════════════════════════════════════════════════════════
maintenance: # SUPPRESSOR / GLOBAL CHAT EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Join/Quit-Nachrichten Unterdrückung und BungeeCord-Messaging
suppressor:
# Aktiviert das Suppressor-System (true = an, false = aus)
enabled: false enabled: false
# Unterdrückt Join- und Quit-Nachrichten für neue Spieler temporär
# true = Nachrichten werden unterdrückt, false = normale Anzeige
# Nützlich um Spam zu vermeiden wenn viele Spieler gleichzeitig joinen/leaven
suppress-join-quit: true
# Dauer der Unterdrückung in Ticks (20 Ticks = 1 Sekunde)
# Nach dieser Zeit werden Join/Quit-Nachrichten wieder normal angezeigt
suppress-duration-ticks: 40
# BungeeCord Plugin-Messaging Channels
# Diese Channels werden für die Kommunikation zwischen Servern verwendet
channels:
# Channel für Join/Quit-Suppression-Control
control: "global:control"
# Channel für globales Chat-Relay über alle Server
chat: "global:chat"
# ══════════════════════════════════════════════════════════════════════════════
# LOGGING EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Konfiguration der Plugin-Logs und Debug-Ausgaben
logging:
# Aktiviert detaillierte Debug-Logs in der Konsole
# true = Sehr ausführliche Logs (nur zur Fehlersuche empfohlen)
# false = Normale Logs
# WARNUNG: Bei true kann die Konsole sehr voll werden!
enable-debug: true
# Pfad zur Log-Datei des Plugins
# Relativ zum Plugin-Ordner
log-file: "logs/plugin.log"
# ══════════════════════════════════════════════════════════════════════════════
# WARTUNGSMODUS
# ══════════════════════════════════════════════════════════════════════════════
# Sperrt den Server für normale Spieler während Wartungsarbeiten
maintenance:
# Aktiviert den Wartungsmodus (true = an, false = aus)
# Wenn aktiviert, können nur Spieler mit der Permission "nexuslobby.maintenance.bypass" joinen
enabled: false
# Nachricht die Spielern angezeigt wird, wenn sie gekickt werden
# Unterstützt Farbcodes und \n für Zeilenumbrüche
kick_message: "&cServer im Wartungsmodus! Du darfst nicht joinen." kick_message: "&cServer im Wartungsmodus! Du darfst nicht joinen."
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# VOID PROTECTION # VOID PROTECTION (SCHUTZ VOR LEERE)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Verhindert, dass Spieler in die Leere fallen # Verhindert dass Spieler in die Leere fallen und im Void sterben
void_protection: void_protection:
# Aktiviert den Void-Schutz (true = an, false = aus)
enabled: true enabled: true
# Teleportiert den Spieler zum Welt-Spawn
# Teleportiert Spieler zum Spawn zurück wenn sie in die Leere fallen
# true = Teleport zum Spawn, false = Spieler nimmt Schaden/stirbt
teleport_to_spawn: true teleport_to_spawn: true
# Nachricht beim Teleport (Leer lassen für keine Nachricht)
# Nachricht die dem Spieler beim Teleport angezeigt wird
# Leer lassen ("") für keine Nachricht
message: "&cDu bist in die Leere gefallen und wurdest teleportiert!" message: "&cDu bist in die Leere gefallen und wurdest teleportiert!"
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# DOUBLE JUMP # DOUBLE JUMP (DOPPELSPRUNG)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Erlaubt einen Doppelsprung in der Lobby # Erlaubt Spielern einen Doppelsprung in der Luft
doublejump: doublejump:
# Aktiviert die Doppelsprung-Funktion (true = an, false = aus)
enabled: true enabled: true
# Stärke des Sprungs nach oben
# Stärke des Sprungs nach oben (vertikale Geschwindigkeit)
# Standardwert: 1.0
# Höhere Werte = höherer Sprung, niedrigere Werte = flacherer Sprung
# Empfohlener Bereich: 0.5 - 2.0
velocity: 1.0 velocity: 1.0
# Vorwärts-Schub beim Sprung
# Vorwärts-Schub beim Sprung (horizontale Geschwindigkeit)
# Standardwert: 0.2
# Höhere Werte = mehr Vorwärtsschub, 0.0 = kein Vorwärtsschub
# Empfohlener Bereich: 0.0 - 0.5
horizontal: 0.2 horizontal: 0.2
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# PLAYER HIDER # PLAYER HIDER (SPIELER VERSTECKEN)
# ----------------------------------------------------- # ══════════════════════════════════════════════════════════════════════════════
# Item, um andere Spieler zu verstecken/anzuzeigen # Item zum Verstecken/Anzeigen anderer Spieler in der Lobby
hider: hider:
# Aktiviert die Player-Hider-Funktion (true = an, false = aus)
enabled: true enabled: true
# Material-Name (Muss ein gültiger Bukkit-Material-Name sein)
# Material des Items im Inventar
# Kann je nach Status wechseln (z.B. grüner/roter Farbstoff)
# Liste: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html
item: "REDSTONE" item: "REDSTONE"
# Slot im Inventar (0-8) # Slot im Inventar (0-8)
slot: 8 slot: 8
# Nachrichten für verschiedene Zustände
messages: messages:
# Anzeigename des Items und Nachricht, wenn alle sichtbar sind # Anzeigename und Nachricht wenn alle Spieler sichtbar sind
# Wird angezeigt wenn der Spieler auf das Item klickt und Spieler sichtbar sind
all: "&aAlle Spieler: &7Sichtbar" all: "&aAlle Spieler: &7Sichtbar"
# Anzeigename des Items und Nachricht, wenn alle versteckt sind
# Anzeigename und Nachricht wenn alle Spieler versteckt sind
# Wird angezeigt wenn der Spieler auf das Item klickt und Spieler versteckt sind
none: "&cKeine Spieler: &7Versteckt" none: "&cKeine Spieler: &7Versteckt"
# ══════════════════════════════════════════════════════════════════════════════
# BALL / SOCCER EINSTELLUNGEN
# ══════════════════════════════════════════════════════════════════════════════
# Aktiviert ein Fußball-System in der Lobby zum Spielen
ball:
# Aktiviert das Ball-System (true = an, false = aus)
enabled: true
# Spawn-Position des Balls
# Tipp: Nutze /nexuslobby ball setspawn um diese Position automatisch zu setzen
spawn:
# Name der Welt in der der Ball spawnt
world: "world"
# X-Koordinate des Ball-Spawns
x: 10.5
# Y-Koordinate des Ball-Spawns
y: 65.0
# Z-Koordinate des Ball-Spawns
z: 10.5
# Blickrichtung des Balls (normalerweise nicht relevant)
yaw: 0.0
pitch: 0.0
# Zeit in Sekunden bis der Ball bei Inaktivität automatisch respawnt
# Nützlich wenn der Ball verloren geht oder stecken bleibt
# 0 = Kein automatischer Respawn
respawn_delay: 60
# ══════════════════════════════════════════════════════════════════════════
# TOR-DEFINITIONEN
# ══════════════════════════════════════════════════════════════════════════
# Tore werden automatisch per Befehl gesetzt und hier gespeichert.
# Manuell bearbeiten ist möglich, aber der Befehlsweg ist empfohlen.
#
# Anleitung zum Einrichten eines Tores:
# 1. Gehe zur INNEREN Ecke des Torrahmens (z.B. linke untere Ecke)
# /nexuslobby ball goal pos1
# 2. Gehe zur GEGENÜBERLIEGENDEN Ecke (rechte obere Ecke, auch 1-2 Blöcke
# hinter dem Tor in Schussrichtung für bessere Erkennung!)
# /nexuslobby ball goal pos2
# 3. Speichern (Team 1 = Blau, Team 2 = Rot):
# /nexuslobby ball goal save torBlau 1
#
# WICHTIG für zuverlässige Erkennung:
# - Die Box muss MINDESTENS 1.5 Blöcke Tiefe in Schussrichtung haben
# - Die Box muss MINDESTENS 3 Blöcke hoch sein (Y-Achse)
# - Nutze /nexuslobby ball goal show um die Box sichtbar zu machen
# - Nutze /nexuslobby ball goal debug um die Ball-Position live zu prüfen
#
# Beispiel-Eintrag (wird vom Plugin automatisch befüllt):
# goals:
# torBlau:
# pos1:
# world: world
# x: 100.0
# y: 64.0
# z: 198.0
# pos2:
# world: world
# x: 106.0
# y: 68.0
# z: 200.5 # <-- 2.5 Blöcke Tiefe in Z-Richtung!
# team: 1
# torRot:
# pos1:
# world: world
# x: 100.0
# y: 64.0
# z: -198.0
# pos2:
# world: world
# x: 106.0
# y: 68.0
# z: -200.5
# team: 2
#
# Tore werden hier automatisch eingetragen sobald du den save-Befehl nutzt:
goals: {}
# ══════════════════════════════════════════════════════════════════════════════
# Links
# ══════════════════════════════════════════════════════════════════════════════
#
# Benötigst du Hilfe oder Support?
# - Webseite: https://m-viper.de
# - Dokumentation: https://viper-network.de/wiki/NexusLobby/
# - Discord: https://discord.com/invite/FdRs4BRd8D
+171
View File
@@ -0,0 +1,171 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# =============================================================
# Dialoge und Gespräche in der Lobby
# =============================================================
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:
+203
View File
@@ -0,0 +1,203 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# =====================================================
# DEFAULT Language
# =====================================================
soccer_module_not_loaded:
de: "§cDas Fußball-Modul ist nicht geladen."
en: "§cThe soccer module is not loaded."
parkour_usage:
de: "§8[§6Nexus§8] §7Nutze: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>"
en: "§8[§6Nexus§8] §7Usage: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>"
parkour_run_aborted:
de: "§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen."
en: "§8[§6Nexus§8] §7Your current run was aborted."
parkour_besttimes_cleared:
de: "§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!"
en: "§8[§6Nexus§8] §aAll parkour best times have been cleared!"
parkour_track_removed:
de: "§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!"
en: "§8[§6Nexus§8] §cThe entire track (checkpoints & finish) has been removed!"
unknown_subcommand:
de: "§cUnbekannter Unterbefehl."
en: "§cUnknown subcommand."
parkour_npc_marked:
de: "§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!"
en: "§8[§6Nexus§8] §aArmorStand marked as parkour NPC!"
parkour_start_set:
de: "§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!"
en: "§8[§6Nexus§8] §aParkour start set at your position!"
scoreboard_usage:
de: "§cBenutzung: /nexus sb <on|off|admin|spieler>"
en: "§cUsage: /nexus sb <on|off|admin|player>"
scoreboard_module_disabled:
de: "§cScoreboard-Modul ist deaktiviert."
en: "§cScoreboard module is disabled."
info_header:
de: "§8§m--------------------------------------"
en: "§8§m--------------------------------------"
info_title:
de: "§6§lNexusLobby §7- Informationen"
en: "§6§lNexusLobby §7- Information"
info_spawn:
de: "§f/spawn §7- Zum Spawn"
en: "§f/spawn §7- To spawn"
info_parkour:
de: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"
en: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"
info_removeall:
de: "§f/nexus parkour removeall §7- Strecke löschen"
en: "§f/nexus parkour removeall §7- Remove track"
info_ball:
de: "§f/nexus ball setspawn §7- Fußball Spawn setzen"
en: "§f/nexus ball setspawn §7- Set soccer spawn"
info_setspawn:
de: "§f/nexus setspawn §7- Spawn setzen"
en: "§f/nexus setspawn §7- Set spawn"
info_scoreboard:
de: "§f/nexus sb <on|off> §7- Scoreboard"
en: "§f/nexus sb <on|off> §7- Scoreboard"
info_reload:
de: "§f/nexus reload §7- Config laden"
en: "§f/nexus reload §7- Reload config"
info_footer:
de: "§8§m--------------------------------------"
en: "§8§m--------------------------------------"
parkour_trainer_greeting:
de: "§6§lTrainer §8» §aViel Erfolg beim Parkour! Gib dein Bestes!"
en: "§6§lTrainer §8» §aGood luck with the parkour! Give it your best!"
update_available:
de: "§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!"
en: "§8[§6Nexus§8] §aA new §6update §afor §eNexusLobby §ais available!"
update_version:
de: "§8» §7Version: §c{old} §8-> §a{new}"
en: "§8» §7Version: §c{old} §8-> §a{new}"
update_download_link:
de: "§8» §6Klicke §e§l[HIER] §6zum Herunterladen."
en: "§8» §6Click §e§l[HERE] §6to download."
update_download_hover:
de: "§7Öffnet die Release-Seite"
en: "§7Opens the release page"
hider_all:
de: "§aAlle Spieler"
en: "§aAll players"
hider_none:
de: "§cKeine Spieler"
en: "§cNo players"
void_protection_message:
de: "§cDu wurdest vor dem Void gerettet!"
en: "§cYou were saved from the void!"
proxy_not_registered:
de: "§cProxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)."
en: "§cProxy connection not possible (BungeeCord channel not registered)."
connecting_to_server:
de: "§eVerbinde zu: §f{server}"
en: "§eConnecting to: §f{server}"
server_connect_error:
de: "§cFehler beim Verbinden zum Server."
en: "§cError connecting to server."
proxy_connect_error:
de: "§cProxy-Verbindung nicht möglich."
en: "§cProxy connection not possible."
armorstand_lookat_required:
de: "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!"
en: "§cYou must look directly at an ArmorStand (crosshair)!"
conv_link_usage:
de: "§cNutze: /nexuscmd conv link <Dialog-ID>"
en: "§cUsage: /nexuscmd conv link <Dialog-ID>"
conv_mark_npcs:
de: "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!"
en: "§cPlease mark at least the first two NPCs (select1 & select2)!"
conv_link_created:
de: "§a§lDauerhafte Verknüpfung erstellt!"
en: "§a§lPermanent link created!"
conv_link_npcs:
de: "§7Beteiligte NPCs: §e{count}"
en: "§7Linked NPCs: §e{count}"
conv_speaker_not_found:
de: "§cFehler: Sprecher 1 nicht gefunden."
en: "§cError: Speaker 1 not found."
conv_unlink_lookat:
de: "§cSchau den NPC an, dessen Verknüpfung du lösen willst!"
en: "§cLook at the NPC you want to unlink!"
conv_unlinked:
de: "§eNPC-Verknüpfung wurde aufgehoben."
en: "§eNPC link has been removed."
conv_start_usage:
de: "§cNutze: /nexuscmd conv start <ID>"
en: "§cUsage: /nexuscmd conv start <ID>"
conv_mark_two_npcs:
de: "§cBitte markiere mindestens zwei NPCs!"
en: "§cPlease mark at least two NPCs!"
npc_bubble_sent:
de: "§aNPC-Sprechblase gesendet."
en: "§aNPC speech bubble sent."
lookat_off:
de: "§cBlickkontakt aus."
en: "§cLook-at disabled."
lookat_on:
de: "§aBlickkontakt an."
en: "§aLook-at enabled."
name_removed:
de: "§eName entfernt."
en: "§eName removed."
name_set:
de: "§7Name gesetzt: {name}"
en: "§7Name set: {name}"
status_backup_saved:
de: "§8(Status-Backup wurde gespeichert)"
en: "§8(Status backup saved)"
command_bound:
de: "§aBefehl gebunden."
en: "§aCommand bound."
no_target:
de: "§cKein Ziel!"
en: "§cNo target!"
commands_and_tags:
de: "§6§lBefehle & Tags:"
en: "§6§lCommands & Tags:"
commands_removed:
de: "§eAlle Befehle gelöscht."
en: "§eAll commands removed."
welcome:
de: "Willkommen auf dem Server!"
en: "Welcome to the server!"
no_permission:
de: "§cKeine Berechtigung."
en: "§cNo permission."
only_player:
de: "§cDieser Befehl ist nur für Spieler!"
en: "§cThis command is for players only!"
parkour_checkpoint_set:
de: "§8[§6Nexus§8] §aParkour-Checkpoint gesetzt!"
en: "§8[§6Nexus§8] §aParkour checkpoint set!"
parkour_finish_set:
de: "§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"
en: "§8[§6Nexus§8] §aParkour finish set!"
teleport_spawn:
de: "§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert."
en: "§8[§6Nexus§8] §aYou have been teleported to spawn."
spawn_world_missing:
de: "§cFehler: Die Spawn-Welt existiert nicht."
en: "§cError: The spawn world does not exist."
spawn_not_set:
de: "§cEs wurde noch kein Spawn gesetzt."
en: "§cNo spawn has been set yet."
plugin_reloaded:
de: "§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!"
en: "§8[§6Nexus§8] §aPlugin successfully reloaded!"
spawn_set:
de: "§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"
en: "§8[§6Nexus§8] §aLobby spawn set successfully!"
silentjoin_on:
de: "§8[§6Nexus§8] §7Silent Join: §aAktiviert"
en: "§8[§6Nexus§8] §7Silent Join: §aEnabled"
silentjoin_off:
de: "§8[§6Nexus§8] §7Silent Join: §cDeaktiviert"
en: "§8[§6Nexus§8] §7Silent Join: §cDisabled"
+77 -18
View File
@@ -1,9 +1,9 @@
name: NexusLobby name: NexusLobby
main: de.nexuslobby.NexusLobby main: de.nexuslobby.NexusLobby
version: "1.0.1" version: "2.0"
api-version: "1.21" api-version: "1.21"
author: M_Viper author: M_Viper
description: Modular Lobby Plugin description: Modular Lobby Plugin with an invisible Particle-Parkour system.
softdepend: [LuckPerms, PlaceholderAPI, Vault, WorldGuard] softdepend: [LuckPerms, PlaceholderAPI, Vault, WorldGuard]
commands: commands:
@@ -12,49 +12,78 @@ commands:
usage: /portal <args> usage: /portal <args>
permission: nexuslobby.portal permission: nexuslobby.portal
permission-message: "§cKeine Rechte!" permission-message: "§cKeine Rechte!"
giveportalwand: giveportalwand:
description: Gibt das Portal-Werkzeug description: Gibt das Portal-Werkzeug
usage: /giveportalwand usage: /giveportalwand
permission: nexuslobby.portal.give permission: nexuslobby.portal.give
permission-message: "§cDu hast keine Berechtigung dafür." permission-message: "§cDu hast keine Berechtigung dafür."
maintenance: maintenance:
description: Aktiviert oder deaktiviert den Wartungsmodus description: Aktiviert oder deaktiviert den Wartungsmodus
usage: /maintenance <on|off> usage: /maintenance <on|off>
permission: nexuslobby.maintenance permission: nexuslobby.maintenance
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
serverswitcher: serverswitcher:
description: Öffnet die Server Switcher GUI description: Öffnet die Server Switcher GUI
usage: /serverswitcher usage: /serverswitcher
permission: nexuslobby.serverswitcher permission: nexuslobby.serverswitcher
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
settings: settings:
description: Öffnet das Lobby-Einstellungsmenü (Gamerules) description: Öffnet das Lobby-Einstellungsmenü (Gamerules)
usage: /settings usage: /settings
permission: nexuslobby.admin permission: nexuslobby.admin
permission-message: "§cDu hast keine Rechte für die Admin-Einstellungen!" permission-message: "§cDu hast keine Rechte für die Admin-Einstellungen!"
build: build:
description: Aktiviert oder deaktiviert den Baumodus description: Aktiviert oder deaktiviert den Baumodus
usage: /build usage: /build
permission: nexuslobby.build permission: nexuslobby.build
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
nexuslobby: nexuslobby:
description: Zeigt Informationen über das Plugin an oder lädt es neu description: Hauptbefehl für Plugin-Verwaltung, Spawn-Setup und Parkour-Konfiguration
usage: /nexuslobby [reload] usage: /nexuslobby [reload|setspawn|silentjoin|sb|parkour]
aliases: [nexus] aliases: [nexus, lobby]
# --- ArmorStandTools Sektion ---
nexustools: nexustools:
description: Nexus ArmorStand Editor description: Nexus ArmorStand Editor (LookAt, Dynamic, etc.)
aliases: [nt, ntools, astools] aliases: [nt, ntools, astools]
nexuscmd: nexuscmd:
description: Nexus Command Binder description: Nexus Command Binder (Slots 0-9) und Gesprächs-System
aliases: [ncmd, ascmd] 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]
permission: nexuslobby.hologram
mapart:
description: Erstellt ein Bild-Raster auf unsichtbaren Rahmen
usage: /mapart <URL> <BxH>
permission: nexuslobby.mapart
intro:
description: Verwalte und teste die Cinematic Intro Tour
usage: /intro <add|clear|start>
permission: nexuslobby.admin
border:
description: Verwalte die unsichtbare Lobby-Begrenzung
usage: /border <circle|square|disable>
permission: nexuslobby.admin
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: permissions:
nexuslobby.portal: nexuslobby.portal:
@@ -70,7 +99,7 @@ permissions:
description: Zugriff auf den Server Switcher description: Zugriff auf den Server Switcher
default: true default: true
nexuslobby.admin: nexuslobby.admin:
description: Voller Zugriff auf Lobby-Gamerules, Einstellungen und Reload description: Voller Zugriff auf Lobby-Gamerules, Einstellungen, Intro, Border, Parkour-Setup und Reload
default: op default: op
nexuslobby.build: nexuslobby.build:
description: Erlaubt das Umgehen des Lobby-Schutzes zum Bauen description: Erlaubt das Umgehen des Lobby-Schutzes zum Bauen
@@ -79,5 +108,35 @@ permissions:
description: Erlaubt die Nutzung der NexusTools GUI description: Erlaubt die Nutzung der NexusTools GUI
default: op default: op
nexuslobby.armorstand.cmd: 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 default: op
+8 -2
View File
@@ -1,6 +1,12 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# ===================================================== # =====================================================
# NEXUSLOBBY – DEFAULT LOBBY GAMERULES # DEFAULT LOBBY GAMERULES
# Minecraft 1.21.1
# ===================================================== # =====================================================
# ------------------------------------------------- # -------------------------------------------------
+8 -1
View File
@@ -1,5 +1,12 @@
# _ __ __ __ __
# / | / /__ _ ____ _______/ / ____ / /_ / /_ __ __
# / |/ / _ \| |/_/ / / / ___/ / / __ \/ __ \/ __ \/ / / /
# / /| / __/> </ /_/ (__ ) /___/ /_/ / /_/ / /_/ / /_/ /
# /_/ |_/\___/_/|_|\__,_/____/_____/\____/_.___/_.___/\__, /
# /____/
#
# ----------------------------------------------------- # -----------------------------------------------------
# NEXUSLOBBY - VISUELLE EINSTELLUNGEN # VISUELLE EINSTELLUNGEN
# ----------------------------------------------------- # -----------------------------------------------------
# --- Tablist Einstellungen --- # --- Tablist Einstellungen ---