13 Commits
1.0.4 ... 1.1.0

Author SHA1 Message Date
7343f2cbf0 Upload pom.xml via GUI 2026-02-01 21:19:41 +00:00
6a892c45db Update from Git Manager GUI 2026-02-01 22:19:40 +01:00
ce43cac14f Update from Git Manager GUI 2026-01-31 21:24:57 +01:00
85aaadbf99 Upload pom.xml via GUI 2026-01-31 20:24:56 +00:00
369e226b3b Upload example-conversations.yml via GUI 2026-01-31 20:24:56 +00:00
ba20c2a498 example-conversations.yml hinzugefügt 2026-01-24 23:12:15 +00:00
250bf2fea6 README.md aktualisiert 2026-01-24 16:13:45 +00:00
64a03b539e Upload pom.xml via GUI 2026-01-24 16:03:51 +00:00
1db87e81f0 Update from Git Manager GUI 2026-01-24 17:03:48 +01:00
4c847401a0 Update from Git Manager GUI 2026-01-23 22:08:30 +01:00
aac2482511 Upload pom.xml via GUI 2026-01-23 21:08:30 +00:00
8ea8ab50f3 Upload pom.xml via GUI 2026-01-23 17:09:05 +00:00
addab7245d Update from Git Manager GUI 2026-01-23 18:09:04 +01:00
35 changed files with 3906 additions and 776 deletions

196
README.md
View File

@@ -4,7 +4,7 @@
<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 Lobby-Plugin für Minecraft Server (Paper/Spigot 1.21+) mit modularem Aufbau, High-End NPC-System, umfangreichen Sicherheitsfunktionen 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)
@@ -16,62 +16,43 @@ Ein umfassendes Lobby-Plugin fur Minecraft Server (Paper/Spigot 1.21+) mit modul
**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 persönlichen Gebrauch gestattet - Die Nutzung ist ausschließlich für den persönlichen Gebrauch gestattet.
- Die Weitergabe, Verbreitung oder Veröffentlichung 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 ausdrückliche 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
- **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn - **Conversation Manager** - Komplexe Dialoge zwischen NPCs mit Sprechblasen und Sound-Effekten.
- **Void-Schutz** - Automatischer Teleport bei Fall ins Void - **Dynamic KI** - NPCs reagieren auf Tageszeit (Fackel nachts) und ziehen bei Annäherung von Spielern das Schwert.
- **Doppelsprung** - Konfigurierbarer Double-Jump mit Cooldown - **LookAt-Logik** - NPCs verfolgen flüssig die Kopfbewegungen von Spielern in der Nähe.
- **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus - **Command Binding** - Binde Spieler-, Konsolen- oder Bungee-Befehle an NPC-Slots (0-9).
- **Status-Backup** - Automatisches Speichern von NPC-Namen via PersistentDataContainer & Tags.
### Visuelle Elemente ### 🌍 Lobby-Management
- **Scoreboard** - Anpassbares Sidebar-Scoreboard mit Animationen - **Spawn-System** - Automatischer Teleport zum Spawn bei Join/Respawn.
- **Tablist** - Header und Footer mit PlaceholderAPI-Support - **Portal-System** - BungeeCord-Portale für nahtlose Server-Wechsel.
- **BossBar** - Animierte Boss-Bar mit wechselnden Nachrichten - **Build-Modus** - Schnelles Umschalten zwischen Bau- und Spielmodus.
- **ActionBar** - Permanente ActionBar-Nachrichten - **Double-Jump** - Konfigurierbarer Doppelsprung mit Cooldown und Partikeln.
### Sicherheit ### 🛡️ Sicherheit & Protection
- **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen (proxycheck.io API) - **VPN-Blocker** - Blockiert VPN/Proxy-Verbindungen via proxycheck.io API.
- **Country-Blocker** - Erlaubt nur bestimmte Lander (Whitelist/Blacklist) - **Country-Blocker** - Geo-IP Filter (Whitelist/Blacklist für Länder).
- **Wartungsmodus** - Sperrt den Server für nicht-berechtigte Spieler - **Maintenance** - Vollständiger Wartungsmodus mit Whitelist-Funktion.
- **Lobby-Schutz** - Verhindert Griefing und unerwünschte Aktionen - **World-Protection** - Schutz vor Griefing, Hunger, Fallschaden und PvP.
### Zusatzliche Module ### 📊 Visuelle Elemente
- **Portal-System** - Erstelle Portale fur Server-Wechsel (BungeeCord) - **Scoreboard & Tablist** - Vollständig animiert mit PlaceholderAPI-Support.
- **ArmorStand-Tools** - Bearbeite ArmorStands mit GUI und Command-Binding - **BossBar & ActionBar** - Rotierende Nachrichten und permanente Status-Anzeigen.
- **Server-Switcher** - GUI-basierter Server-Wechsel - **Hologramme** - Einfache Erstellung von Text-Displays in der Lobby.
- **Spieler verstecken** - Toggle fur Spieler-Sichtbarkeit
- **Chat-Suppressor** - Unterdrückung fur globalen Chat
---
## Installation
### Voraussetzungen
- Paper/Spigot Server 1.21 oder höher
- Java 21 oder hoher
### Optionale Abhangigkeiten
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) - Für Platzhalter-Support
- [LuckPerms](https://luckperms.net/) - Für Berechtigungsverwaltung
### Schritte
1. Lade `NexusLobby.jar` herunter
2. Kopiere die JAR in den `plugins/` Ordner deines Servers
3. Starte den Server neu
4. Konfiguriere das Plugin in `plugins/NexusLobby/`
--- ---
@@ -79,73 +60,31 @@ Bei Verstoss gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
| Befehl | Beschreibung | Berechtigung | | Befehl | Beschreibung | Berechtigung |
|--------|--------------|--------------| |--------|--------------|--------------|
| `/nexuslobby` | Hauptbefehl mit Hilfe | `nexuslobby.use` | | `/nexuslobby` | Hauptbefehl (reload, setspawn, silentjoin) | `nexuslobby.admin` |
| `/nexuslobby reload` | Konfiguration neu laden | `nexuslobby.reload` | | `/nexuscmd` | NPC Command/Dialog Verwaltung (aliases: `ncmd`, `conv`) | `nexuslobby.armorstand.cmd` |
| `/nexuslobby setspawn` | Spawn-Punkt setzen | `nexuslobby.setspawn` | | `/nexustools` | NPC Editor GUI (Rotation, KI, Sichtbarkeit) | `nexuslobby.armorstand.use` |
| `/build` | Build-Modus umschalten | `nexuslobby.build` | | `/build` | Aktiviert/Deaktiviert den Baumodus | `nexuslobby.build` |
| `/maintenance` | Wartungsmodus verwalten | `nexuslobby.maintenance` | | `/maintenance` | Schaltet den Wartungsmodus (on/off) | `nexuslobby.maintenance` |
| `/portal` | Portal-System verwalten | `nexuslobby.portal` | | `/portal` | Verwaltung der Server-Portale | `nexuslobby.portal` |
| `/armorstand` | ArmorStand-Editor | `nexuslobby.armorstand` | | `/holo` | Erstellt oder löscht Text-Hologramme | `nexuslobby.hologram` |
| `/lobbysettings` | Spieler-Einstellungen | `nexuslobby.settings` | | `/mapart` | Erstellt Bilder aus URLs auf Karten-Rahmen | `nexuslobby.mapart` |
--- ---
## Konfiguration ## Konfiguration (Auszug)
### config.yml ### conversations.yml
```yaml ```yaml
prefix: "&8[&6NexusLobby&8] " conversations:
willkommen:
dialogue:
- "&eWächter: &7Willkommen auf dem Server!"
- "&aBürger: &7Schön, dass du da bist!"
spawn: links:
teleport_on_join: true UUID-NPC1:
teleport_on_respawn: true partner: UUID-NPC2
world: "world" dialog: willkommen
x: 0.5
y: 100.0
z: 0.5
double_jump:
enabled: true
cooldown: 3
velocity_y: 1.0
velocity_multiplier: 1.5
```
### settings.yml
```yaml
player_visibility: true
double_jump: true
flight: false
gamerules:
block_break: false
block_place: false
pvp: false
hunger: false
fall_damage: false
```
### visuals.yml
```yaml
scoreboard:
enabled: true
title: "&6&lNEXUS LOBBY"
lines:
- "&7"
- "&fSpieler: &a%online%"
- "&fRang: &e%luckperms_primary_group_name%"
tablist:
enabled: true
header:
- "&6&lNEXUS NETWORK"
footer:
- "&7Spieler online: &a%online%"
bossbar:
enabled: true
messages:
- "&6Willkommen auf NexusNetwork!"
``` ```
--- ---
@@ -154,47 +93,28 @@ bossbar:
| 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` | Portale verwalten |
| `nexuslobby.armorstand` | ArmorStand-Editor nutzen |
### 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-Blocker umgehen |
--- ---
## PlaceholderAPI ## Support & Kontakt
Das Plugin registriert eigene Platzhalter unter der Expansion `nexuslobby`: - **Wiki:** Ausführliche Dokumentation der Module im [Wiki](../../../wiki).
- **Bug-Reports:** Erstelle ein [Issue](../../../issues) bei technischen Problemen.
| Platzhalter | Beschreibung |
|-------------|--------------|
| `%nexuslobby_online%` | Spieler online |
| `%nexuslobby_max_players%` | Maximale Spieler |
| `%nexuslobby_maintenance%` | Wartungsmodus Status |
| `%nexuslobby_build_mode%` | Build-Modus Status |
| `%nexuslobby_double_jump%` | Double-Jump Status |
--- ---
## Support **Copyright (c) 2026 - M_Viper - Alle Rechte vorbehalten**
- [Wiki](../../../wiki) - Ausführliche Dokumentation
- [Issues](../../../issues) - Bug-Reports und Feature-Requests
---
**Copyright (c) 2025 - Alle Rechte vorbehalten**
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt. Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.

164
example-conversations.yml Normal file
View File

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

View File

@@ -6,7 +6,7 @@
<groupId>de.nexuslobby</groupId> <groupId>de.nexuslobby</groupId>
<artifactId>NexusLobby</artifactId> <artifactId>NexusLobby</artifactId>
<version>1.0.4</version> <version>1.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>NexusLobby</name> <name>NexusLobby</name>

View File

@@ -13,11 +13,17 @@ 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.modules.gadgets.GadgetModule; import de.nexuslobby.modules.gadgets.GadgetModule;
import de.nexuslobby.modules.hologram.HologramModule; import de.nexuslobby.modules.hologram.HologramModule;
import de.nexuslobby.modules.mapart.MapArtModule; // Neu import de.nexuslobby.modules.mapart.MapArtModule;
import de.nexuslobby.modules.intro.IntroModule; // Neu import de.nexuslobby.modules.intro.IntroModule;
import de.nexuslobby.modules.border.BorderModule;
import de.nexuslobby.modules.parkour.ParkourManager;
import de.nexuslobby.modules.parkour.ParkourListener;
import de.nexuslobby.modules.player.PlayerInspectModule;
import de.nexuslobby.modules.ball.SoccerModule; // NEU
import de.nexuslobby.utils.*; import 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;
@@ -26,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 {
@@ -50,8 +64,13 @@ public class NexusLobby extends JavaPlugin implements Listener {
private GadgetModule gadgetModule; private GadgetModule gadgetModule;
private HologramModule hologramModule; private HologramModule hologramModule;
private DynamicArmorStandModule dynamicArmorStandModule; private DynamicArmorStandModule dynamicArmorStandModule;
private MapArtModule mapArtModule; // Neu private MapArtModule mapArtModule;
private IntroModule introModule; // Neu private IntroModule introModule;
private BorderModule borderModule;
private ParkourManager parkourManager;
private SoccerModule soccerModule; // NEU
private ConversationManager conversationManager;
private File visualsFile; private File visualsFile;
private FileConfiguration visualsConfig; private FileConfiguration visualsConfig;
@@ -59,10 +78,28 @@ 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() { // NEU
return soccerModule;
}
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
@@ -72,12 +109,23 @@ public class NexusLobby extends JavaPlugin implements Listener {
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();
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.");
@@ -86,27 +134,25 @@ public class NexusLobby extends JavaPlugin implements Listener {
registerCommands(); registerCommands();
checkUpdates(); checkUpdates();
getLogger().info("NexusLobby wurde erfolgreich gestartet."); getLogger().info("NexusLobby v" + getDescription().getVersion() + " 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();
} }
@@ -116,18 +162,45 @@ 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();
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 -> {
if (!this.getDescription().getVersion().equalsIgnoreCase(version)) {
this.updateAvailable = true;
this.latestVersion = version;
getLogger().warning("====================================================");
getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version);
getLogger().warning("====================================================");
} else {
getLogger().info("NexusLobby ist aktuell (v" + version + ").");
}
});
}
private void registerModules() { private void registerModules() {
moduleManager.registerModule(new ProtectionModule()); moduleManager.registerModule(new ProtectionModule());
moduleManager.registerModule(new ScoreboardModule()); moduleManager.registerModule(new ScoreboardModule());
@@ -141,28 +214,38 @@ public class NexusLobby extends JavaPlugin implements Listener {
this.hologramModule = new HologramModule(); this.hologramModule = new HologramModule();
moduleManager.registerModule(this.hologramModule); moduleManager.registerModule(this.hologramModule);
// Dynamic ArmorStand Module
this.dynamicArmorStandModule = new DynamicArmorStandModule(); this.dynamicArmorStandModule = new DynamicArmorStandModule();
moduleManager.registerModule(this.dynamicArmorStandModule); moduleManager.registerModule(this.dynamicArmorStandModule);
// MapArt & Intro Module moduleManager.registerModule(new ArmorStandStatusModule());
this.mapArtModule = new MapArtModule(); this.mapArtModule = new MapArtModule();
moduleManager.registerModule(this.mapArtModule); moduleManager.registerModule(this.mapArtModule);
this.introModule = new IntroModule(); this.introModule = new IntroModule();
moduleManager.registerModule(this.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(); // NEU
moduleManager.registerModule(this.soccerModule); // NEU
this.portalManager = new PortalManager(this);
moduleManager.registerModule(portalManager); moduleManager.registerModule(portalManager);
} }
@@ -174,6 +257,24 @@ 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);
}
public class NPCClickListener implements Listener {
@EventHandler
public void onNPCClick(PlayerInteractAtEntityEvent event) {
Entity entity = event.getRightClicked();
Player player = event.getPlayer();
if (entity instanceof ArmorStand as) {
if (as.getScoreboardTags().contains("parkour_trainer")) {
player.performCommand("warp parkour");
player.sendMessage("§6§lTrainer §8» §aViel Erfolg beim Parkour! Gib dein Bestes!");
}
}
}
} }
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
@@ -181,6 +282,8 @@ public class NexusLobby extends JavaPlugin implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
event.setJoinMessage(null); event.setJoinMessage(null);
teleportToSpawn(player);
player.getInventory().clear(); player.getInventory().clear();
player.getInventory().setArmorContents(null); player.getInventory().setArmorContents(null);
@@ -201,80 +304,86 @@ public class NexusLobby extends JavaPlugin implements Listener {
TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen."); TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen.");
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases")); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases"));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder("§7Öffnet die Gitea Release-Seite").create())); new ComponentBuilder("§7Öffnet die Release-Seite").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);
}
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);
}
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() {
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
if (moduleManager != null) moduleManager.disableAll(); if (moduleManager != null) {
getLogger().info("NexusLobby disabled"); moduleManager.disableAll();
}
getLogger().info("NexusLobby deaktiviert.");
} }
private void registerCommands() { private void registerCommands() {
LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule); 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 holoCmd = this.getCommand("holo"); if (getCommand("holo") != null) {
if (holoCmd != null) { getCommand("holo").setExecutor(new HoloCommand(hologramModule));
holoCmd.setExecutor(new HoloCommand(hologramModule)); getCommand("holo").setTabCompleter(tabCompleter);
holoCmd.setTabCompleter(tabCompleter);
} }
PluginCommand maintenanceCmd = this.getCommand("maintenance"); if (getCommand("maintenance") != null) {
if (maintenanceCmd != null) { getCommand("maintenance").setExecutor(new MaintenanceCommand());
maintenanceCmd.setExecutor(new MaintenanceCommand()); getCommand("maintenance").setTabCompleter(tabCompleter);
maintenanceCmd.setTabCompleter(tabCompleter);
} }
if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this)); if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this));
@@ -291,20 +400,28 @@ 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);
} }
// Neue Commands im TabCompleter registrieren
if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter); if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter);
if (getCommand("introtest") != null) getCommand("introtest").setTabCompleter(tabCompleter); if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter);
if (getCommand("border") != null) {
getCommand("border").setExecutor(new BorderCommand());
getCommand("border").setTabCompleter(tabCompleter);
}
} }
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; }
@@ -314,15 +431,23 @@ 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";
}
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 ItemsModule getItemsModule() { return itemsModule; }
public GadgetModule getGadgetModule() { return gadgetModule; } public GadgetModule getGadgetModule() { return gadgetModule; }
public HologramModule getHologramModule() { return hologramModule; } public HologramModule getHologramModule() { return hologramModule; }
public DynamicArmorStandModule getDynamicArmorStandModule() { return dynamicArmorStandModule; } public DynamicArmorStandModule getDynamicArmorStandModule() { return dynamicArmorStandModule; }
public MapArtModule getMapArtModule() { return mapArtModule; }
public IntroModule getIntroModule() { return introModule; }
public BorderModule getBorderModule() { return borderModule; }
} }

View File

@@ -1,5 +1,6 @@
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 de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -25,100 +26,147 @@ public class LobbyTabCompleter implements TabCompleter {
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull 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) ---
if (command.getName().equalsIgnoreCase("nexuslobby") || command.getName().equalsIgnoreCase("nexus")) { if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) {
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")); // NEU: ball
}
suggestions.add("sb"); suggestions.add("sb");
} else if (args.length == 2 && args[0].equalsIgnoreCase("sb")) { } else if (args.length == 2) {
suggestions.add("on"); if (args[0].equalsIgnoreCase("sb")) {
suggestions.add("off"); suggestions.addAll(Arrays.asList("on", "off"));
if (sender.hasPermission("nexuslobby.scoreboard.admin")) { if (sender.hasPermission("nexuslobby.scoreboard.admin")) {
suggestions.add("admin"); suggestions.addAll(Arrays.asList("admin", "spieler"));
suggestions.add("spieler"); }
} else if (args[0].equalsIgnoreCase("silentjoin")) {
suggestions.addAll(Arrays.asList("on", "off"));
} else if (args[0].equalsIgnoreCase("parkour")) {
suggestions.addAll(Arrays.asList("setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall"));
} else if (args[0].equalsIgnoreCase("ball")) { // NEU: Ball Subcommands
if (sender.hasPermission("nexuslobby.admin")) {
suggestions.addAll(Arrays.asList("setspawn", "reload"));
}
}
} else if (args.length == 3) {
if (args[0].equalsIgnoreCase("parkour") && args[1].equalsIgnoreCase("setcheckpoint")) {
suggestions.addAll(Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9"));
} }
} }
} }
// --- Hologram Befehl --- // --- Hologram Befehl ---
else if (command.getName().equalsIgnoreCase("holo")) { 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");
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (hologramModule != null) {
suggestions.addAll(hologramModule.getHologramIds()); suggestions.addAll(hologramModule.getHologramIds());
} }
} }
}
// --- Wartungsmodus --- // --- Wartungsmodus ---
else if (command.getName().equalsIgnoreCase("maintenance")) { else if (cmdName.equals("maintenance")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("on"); suggestions.addAll(Arrays.asList("on", "off"));
suggestions.add("off");
} }
} }
// --- Portalsystem --- // --- Portalsystem ---
else if (command.getName().equalsIgnoreCase("portal")) { else if (cmdName.equals("portal")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("create"); suggestions.addAll(Arrays.asList("create", "delete", "list"));
suggestions.add("delete");
suggestions.add("list");
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (portalManager != null) {
suggestions.addAll(portalManager.getPortalNames()); suggestions.addAll(portalManager.getPortalNames());
} }
} }
}
// --- MapArt --- // --- MapArt ---
else if (command.getName().equalsIgnoreCase("mapart")) { else if (cmdName.equals("mapart")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("https://"); suggestions.add("https://");
} else if (args.length == 2) { } else if (args.length == 2) {
suggestions.add("1x1"); suggestions.addAll(Arrays.asList("1x1", "3x2", "6x4", "8x5"));
suggestions.add("3x2");
suggestions.add("6x4");
} }
} }
// --- Intro System --- // --- Intro System ---
else if (command.getName().equalsIgnoreCase("intro")) { else if (cmdName.equals("intro")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("add"); suggestions.addAll(Arrays.asList("add", "clear", "start"));
suggestions.add("clear");
suggestions.add("start");
} }
} }
// --- NexusTools --- // --- WorldBorder ---
else if (command.getName().equalsIgnoreCase("nexustools") || command.getName().equalsIgnoreCase("astools") || command.getName().equalsIgnoreCase("nt")) { else if (cmdName.equals("border")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("reload"); suggestions.addAll(Arrays.asList("circle", "square", "disable"));
suggestions.add("dynamic"); } else if (args.length == 2 && args[0].equalsIgnoreCase("circle")) {
suggestions.addAll(Arrays.asList("10", "25", "50", "100", "250"));
} }
} }
// --- NexusCmd --- // --- NexusCmd / ArmorStandTools ---
else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ascmd") || command.getName().equalsIgnoreCase("ncmd")) { else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd") || cmdName.equals("conv")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("name"); suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say"));
suggestions.add("list"); }
suggestions.add("add"); else if (args.length == 2) {
suggestions.add("remove"); if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("name")) {
suggestions.addAll(Arrays.asList("<Anzeigename>", "none"));
} else if (args[0].equalsIgnoreCase("conv")) {
suggestions.addAll(Arrays.asList("select1", "select2", "select3", "select4", "link", "unlink", "start"));
} else if (args[0].equalsIgnoreCase("remove")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "all"));
} else if (args[0].equalsIgnoreCase("say")) {
suggestions.add("<Nachricht>");
}
}
else if (args.length == 3) {
if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("conv")) {
if (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link")) {
if (NexusLobby.getInstance().getConversationManager() != null) {
suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds());
}
} }
else if (args.length == 2 && args[0].equalsIgnoreCase("name")) {
suggestions.add("none");
} }
else if (args.length == 2 && args[0].equalsIgnoreCase("add")) {
suggestions.add("0");
} }
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) { else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
suggestions.add("player"); suggestions.addAll(Arrays.asList("bungee", "console", "player"));
suggestions.add("console"); }
suggestions.add("bungee"); else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) {
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) {
suggestions.addAll(NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false));
}
} }
} }
// --- ArmorStandTools Alternate ---
else if (cmdName.equals("astools") || cmdName.equals("nt") || cmdName.equals("ntools")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("dynamic", "lookat", "addplayer", "addconsole", "remove", "reload", "say"));
}
else if (args.length == 2 && args[0].equalsIgnoreCase("say")) {
suggestions.add("<Nachricht>");
}
else if (args.length == 2 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
}
else if (args.length == 3 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
}
}
// Filtert die Liste basierend auf der bisherigen Eingabe
return suggestions.stream() 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());

View File

@@ -2,89 +2,240 @@ 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.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")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§cDieser Befehl ist nur für Spieler!"); 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);
handleSetStart(player, pm);
return true;
}
if (cmdName.equalsIgnoreCase("setcheckpoint")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setCheckpoint(player, player.getLocation());
return true;
}
if (cmdName.equalsIgnoreCase("setfinish")) {
if (!player.hasPermission("nexuslobby.admin")) return noPerm(player);
pm.setFinishLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!");
return true;
}
// --- SPAWN BEFEHL ---
if (cmdName.equalsIgnoreCase("spawn")) {
FileConfiguration config = NexusLobby.getInstance().getConfig();
if (config.contains("spawn.world")) {
Location loc = getSpawnFromConfig(config);
if (loc != null) {
player.teleport(loc);
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.2f);
player.sendMessage("§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert.");
} else {
player.sendMessage("§cFehler: Die Spawn-Welt existiert nicht.");
}
} else {
player.sendMessage("§cEs wurde noch kein Spawn gesetzt.");
}
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("§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f);
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("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!");
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("§8[§6Nexus§8] §7Silent Join: §cDeaktiviert");
} else {
NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §7Silent Join: §aAktiviert");
}
break;
case "sb":
handleScoreboard(player, args);
break;
case "ball": // NEU: Weiterleitung an das SoccerModule
if (NexusLobby.getInstance().getSoccerModule() != null) {
return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args);
}
player.sendMessage("§cDas Fußball-Modul ist nicht geladen.");
break;
case "parkour":
if (args.length < 2) {
player.sendMessage("§8[§6Nexus§8] §7Nutze: §e/nexus parkour <setstart|setfinish|setcheckpoint|reset|clear|removeall>");
return true; return true;
} }
String sub = args[1].toLowerCase(); String sub = args[1].toLowerCase();
if (!player.hasPermission("nexuslobby.admin") && !sub.equals("reset")) return noPerm(player);
switch (sub) { switch (sub) {
case "on": case "setstart":
sbModule.setVisibility(player, true); handleSetStart(player, pm);
break; break;
case "off": case "setfinish":
sbModule.setVisibility(player, false); pm.setFinishLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!");
break; break;
case "admin": case "setcheckpoint":
if (!player.hasPermission("nexuslobby.scoreboard.admin")) { pm.setCheckpoint(player, player.getLocation());
player.sendMessage("§cKeine Rechte für den Admin-Modus.");
return true;
}
sbModule.setAdminMode(player, true);
break; break;
case "spieler": case "reset":
if (!player.hasPermission("nexuslobby.scoreboard.admin")) { pm.stopParkour(player);
player.sendMessage("§cKeine Rechte für den Admin-Modus."); player.sendMessage("§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen.");
return true; break;
} case "clear":
sbModule.setAdminMode(player, false); pm.clearStats();
player.sendMessage("§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!");
break;
case "removeall":
pm.removeAllPoints();
player.sendMessage("§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!");
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f);
break; break;
default: default:
player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>"); player.sendMessage("§cUnbekannter Unterbefehl.");
break; break;
} }
break;
default:
sendInfo(player);
break;
}
return true; return true;
} }
// Sub-Befehl: /nexus reload private void handleSetStart(Player player, ParkourManager pm) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { // NPC Erkennung (Blickrichtung auf ArmorStand)
if (!sender.hasPermission("nexuslobby.admin")) { ArmorStand targetAs = null;
sender.sendMessage("§cDu hast keine Berechtigung für diesen Befehl."); 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("§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!");
}
pm.setStartLocation(player.getLocation());
player.sendMessage("§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!");
}
private boolean noPerm(Player player) {
player.sendMessage("§cKeine Berechtigung.");
return true; return true;
} }
sender.sendMessage("§7[§6NexusLobby§7] §eKonfigurationen und Module werden neu geladen..."); private void handleScoreboard(Player player, String[] args) {
NexusLobby.getInstance().reloadPlugin(); if (args.length < 2) {
sender.sendMessage("§7[§6NexusLobby§7] §aDas Plugin wurde erfolgreich neu geladen!"); player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>");
return true; return;
}
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) {
player.sendMessage("§cScoreboard-Modul ist deaktiviert.");
return;
}
String sub = args[1].toLowerCase();
switch (sub) {
case "on": sbModule.setVisibility(player, true); break;
case "off": sbModule.setVisibility(player, false); break;
case "admin":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true);
else player.sendMessage("§cKeine Rechte.");
break;
case "spieler":
if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false);
else player.sendMessage("§cKeine Rechte.");
break;
}
} }
// Standard-Info private Location getSpawnFromConfig(FileConfiguration config) {
String version = NexusLobby.getInstance().getDescription().getVersion(); World world = Bukkit.getWorld(config.getString("spawn.world", "world"));
sender.sendMessage("§8§m--------------------------------------"); if (world == null) return null;
sender.sendMessage("§6§lNexusLobby §7- Informationen"); return new Location(world,
sender.sendMessage(""); config.getDouble("spawn.x"), config.getDouble("spawn.y"), config.getDouble("spawn.z"),
sender.sendMessage("§ePlugin Name: §fNexusLobby"); (float) config.getDouble("spawn.yaw"), (float) config.getDouble("spawn.pitch"));
sender.sendMessage("§eVersion: §f" + version);
sender.sendMessage("§eAutor: §fM_Viper");
sender.sendMessage("");
sender.sendMessage("§6Befehle:");
sender.sendMessage("§f/nexus reload §7- Plugin neu laden");
sender.sendMessage("§f/nexus sb <on|off> §7- Scoreboard schalten");
if (sender.hasPermission("nexuslobby.scoreboard.admin")) {
sender.sendMessage("§f/nexus sb <admin|spieler> §7- Modus wechseln");
} }
sender.sendMessage("§8§m--------------------------------------");
return true; private void sendInfo(Player player) {
player.sendMessage("§8§m--------------------------------------");
player.sendMessage("§6§lNexusLobby §7- Informationen");
player.sendMessage("");
player.sendMessage("§f/spawn §7- Zum Spawn");
player.sendMessage("§f/setstart §8| §f/setcheckpoint §8| §f/setfinish");
player.sendMessage("§f/nexus parkour removeall §7- Strecke löschen");
player.sendMessage("§f/nexus ball setspawn §7- Fußball Spawn setzen"); // NEU
player.sendMessage("§f/nexus setspawn §7- Spawn setzen");
player.sendMessage("§f/nexus sb <on|off> §7- Scoreboard");
player.sendMessage("§f/nexus reload §7- Config laden");
player.sendMessage("§8§m--------------------------------------");
} }
} }

View File

@@ -1,8 +1,8 @@
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;
@@ -14,8 +14,9 @@ import org.bukkit.event.inventory.InventoryClickEvent;
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 {
@@ -26,7 +27,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 +37,152 @@ 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.
* Nutzt nun die Gruppen-Logik (z.B. owner_suche).
*/
private void checkAndTriggerDialog(ArmorStand as, Player p) {
String groupName = null;
String partnerUUIDString = null;
for (String tag : as.getScoreboardTags()) {
// conv_id enthält jetzt den Gruppennamen aus der conversations.yml
if (tag.startsWith("conv_id:")) groupName = tag.split(":")[1];
if (tag.startsWith("conv_partner:")) partnerUUIDString = tag.split(":")[1];
}
if (groupName != null && partnerUUIDString != null) {
try {
UUID partnerUUID = UUID.fromString(partnerUUIDString);
// Wir rufen playConversation auf. Der Manager entscheidet selbst
// anhand der Uhrzeit, ob er morgens, mittags oder nachts abspielt.
NexusLobby.getInstance().getConversationManager().playConversation(
as.getUniqueId(),
partnerUUID,
groupName
);
} catch (Exception ignored) {
// Falls die UUID oder Gruppe ungültig ist
}
}
}
/**
* Sendet den Spieler per Plugin-Message an BungeeCord
*/
private void connectToServer(Player player, String server) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(server);
player.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", out.toByteArray());
player.sendMessage("§8[§6Nexus§8] §7Du wirst mit §e" + server + " §7verbunden...");
}
@EventHandler(priority = EventPriority.LOW) @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;
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.) // Menü aktualisieren, falls wir noch im selben Editor sind
if (p.getOpenInventory().getTitle().equals("Nexus ArmorStand Editor")) { if (p.getOpenInventory().getTitle().equals(title)) {
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);
}
} }
} }

View File

@@ -1,17 +1,30 @@
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 final String prefix = "§8[§6Nexus§8] ";
@@ -25,15 +38,163 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
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 + "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!");
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 + "§cNutze: /nexuscmd conv link <Dialog-ID>");
return true;
}
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!");
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 + "§a§lDauerhafte Verknüpfung erstellt!");
p.sendMessage(prefix + "§7Beteiligte NPCs: §e" + (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 + "§cFehler: Sprecher 1 nicht gefunden.");
}
break;
case "unlink":
ArmorStand targetUnlink = getTargetArmorStand(p);
if (targetUnlink == null) {
p.sendMessage(prefix + "§cSchau den NPC an, dessen Verknüpfung du lösen willst!");
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 + "§eNPC-Verknüpfung wurde aufgehoben.");
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 + "§cNutze: /nexuscmd conv start <ID>");
return true;
}
if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) {
p.sendMessage(prefix + "§cBitte markiere mindestens zwei 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 + "§cSchau einen ArmorStand an!"); 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 + "§aNPC-Sprechblase gesendet.");
return true;
}
if (args[0].equalsIgnoreCase("lookat")) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; }
if (target.getScoreboardTags().contains("as_lookat")) {
target.removeScoreboardTag("as_lookat");
target.setHeadPose(new EulerAngle(0, 0, 0));
p.sendMessage(prefix + "§cBlickkontakt aus.");
} else {
target.addScoreboardTag("as_lookat");
p.sendMessage(prefix + "§aBlickkontakt an.");
}
return true;
}
if (args[0].equalsIgnoreCase("name") && args.length >= 2) {
if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); 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);
@@ -42,57 +203,55 @@ public class ArmorStandCmdExecutor implements CommandExecutor {
String colored = ChatColor.translateAlternateColorCodes('&', nameInput); String colored = ChatColor.translateAlternateColorCodes('&', nameInput);
target.setCustomName(colored); target.setCustomName(colored);
target.setCustomNameVisible(true); target.setCustomNameVisible(true);
// 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 + "§7Name gesetzt: " + colored); p.sendMessage(prefix + "§7Name gesetzt: " + colored);
p.sendMessage(prefix + "§8(Status-Backup wurde gespeichert)");
} }
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 + "§cSchau einen ArmorStand an!"); 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 + "§aBefehl gebunden.");
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(prefix + "§cKein Ziel!"); return true; }
String cmd = buildString(args, 4); p.sendMessage("§6§lBefehle & 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(prefix + "§cKein Ziel!"); return true; }
return true; target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
} p.sendMessage(prefix + "§eAlle Befehle gelöscht.");
if (args.length >= 1 && args[0].equalsIgnoreCase("list")) {
p.sendMessage("§6§lBefehle auf diesem ArmorStand:");
for (String tag : target.getScoreboardTags()) {
if (tag.startsWith("ascmd:")) {
p.sendMessage(" §8» §e" + tag.replace("ascmd:", ""));
}
}
return true;
}
if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) {
Set<String> tags = target.getScoreboardTags();
for (String tag : new ArrayList<>(tags)) {
if (tag.startsWith("ascmd:")) target.removeScoreboardTag(tag);
}
p.sendMessage(prefix + "§eAlle Befehle entfernt.");
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 +264,26 @@ 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");
return true; return true;
} }
} }

View File

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

View File

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

View File

@@ -0,0 +1,108 @@
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();
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();
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();
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);
}
}
}

View File

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

View File

@@ -6,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 -> {
p.closeInventory();
p.sendMessage("§8[§6Nexus§8] §7Nutze §e/nexuscmd name <Text>");
AST.selectedArmorStand.put(p.getUniqueId(), as);
}
case CONV_SETUP -> {
// Automatisches Markieren via GUI
if (!p.hasMetadata("conv_npc1")) {
p.setMetadata("conv_npc1", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString()));
p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 1 markiert.");
} else {
p.setMetadata("conv_npc2", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString()));
p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 2 markiert.");
p.sendMessage("§8[§6Nexus§8] §7Nutze nun §e/nexuscmd conv start <ID>");
}
p.closeInventory();
}
case REMOVE -> {
as.remove();
p.closeInventory();
p.sendMessage("§cNexus ArmorStand entfernt.");
}
case OPEN_POSE_EDITOR -> ArmorStandPoseGUI.openPartSelectionMenu(p, as);
default -> {}
}
}
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);
} }
@@ -116,6 +101,8 @@ 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() || !item.getItemMeta().hasDisplayName()) return null;
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace(" ", "_"); String name = ChatColor.stripColor(item.getItemMeta().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; }
} }
} }

View File

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

View File

@@ -3,25 +3,20 @@ package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby; 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.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.World; import org.bukkit.World;
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.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.EulerAngle;
import java.time.LocalTime;
/**
* DynamicArmorStandModule
* Reagiert auf Uhrzeit und Wetter.
* Nutzt PersistentData, damit die Einstellung auch nach einem Server-Neustart bleibt.
*/
public class DynamicArmorStandModule implements Module { public class DynamicArmorStandModule implements Module {
// Der Key muss mit dem Command übereinstimmen oder das Modul nutzt Tags.
// Wir nutzen hier PersistentData, da es sicherer ist als Tags.
private final NamespacedKey npcKey = new NamespacedKey(NexusLobby.getInstance(), "dynamic_npc"); private final NamespacedKey npcKey = new NamespacedKey(NexusLobby.getInstance(), "dynamic_npc");
@Override @Override
@@ -32,88 +27,106 @@ public class DynamicArmorStandModule implements Module {
@Override @Override
public void onEnable() { public void onEnable() {
startUpdateTask(); startUpdateTask();
Bukkit.getLogger().info("[NexusLobby] DynamicArmorStandModule wurde aktiviert."); Bukkit.getLogger().info("§a[NexusLobby] DynamicArmorStandModule aktiv: Ingame-Zeit & Sternen-Effekt geladen.");
} }
/**
* Startet einen Timer, der alle 5 Sekunden (100 Ticks) prüft.
*/
private void startUpdateTask() { private void startUpdateTask() {
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { // Alle 5 Ticks (0.25s) für flüssige Partikel
updateAllArmorStands(); Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::updateAllArmorStands, 20L, 5L);
}, 100L, 100L); // Alle 5 Sekunden statt 2 Minuten
} }
public void updateAllArmorStands() { public void updateAllArmorStands() {
int hour = LocalTime.now().getHour();
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
boolean isRaining = world.hasStorm(); long time = world.getTime();
boolean isIngameNight = (time >= 13000 && time <= 23000);
for (Entity entity : world.getEntities()) { for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand armorStand) { if (entity instanceof ArmorStand as) {
// PRÜFUNG: Entweder PersistentData ODER Scoreboard-Tag "as_dynamic" if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE) ||
if (armorStand.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE) || as.getScoreboardTags().contains("as_dynamic")) {
armorStand.getScoreboardTags().contains("as_dynamic")) {
applyDynamicChanges(armorStand, hour, isRaining); applyDynamicChanges(as, isIngameNight);
if (isIngameNight && as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
spawnStarParticles(as);
}
} }
} }
} }
} }
} }
private void applyDynamicChanges(ArmorStand as, int hour, boolean isRaining) { private void applyDynamicChanges(ArmorStand as, boolean isNight) {
// --- ZEIT-LOGIK (Nacht: 20:00 - 06:00) --- if (!as.hasArms()) as.setArms(true);
if (hour >= 20 || hour <= 6) {
// --- 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) { if (as.getEquipment().getItemInOffHand().getType() != Material.TORCH) {
as.getEquipment().setItemInOffHand(new ItemStack(Material.TORCH)); as.getEquipment().setItemInOffHand(new ItemStack(Material.TORCH));
as.setLeftArmPose(new EulerAngle(Math.toRadians(-50), Math.toRadians(15), 0));
} }
} else { } else {
if (as.getEquipment().getItemInOffHand().getType() == Material.TORCH) { if (as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
as.getEquipment().setItemInOffHand(null); as.getEquipment().setItemInOffHand(null);
} as.setLeftArmPose(EulerAngle.ZERO);
}
// --- WETTER-LOGIK (Regen) ---
if (isRaining) {
if (as.getEquipment().getHelmet() == null || as.getEquipment().getHelmet().getType() != Material.LEATHER_HELMET) {
as.getEquipment().setHelmet(new ItemStack(Material.LEATHER_HELMET));
}
} else {
// Nur ausziehen, wenn es hellwach ist und nicht regnet
if (hour < 20 && hour > 6) {
if (as.getEquipment().getHelmet() != null && as.getEquipment().getHelmet().getType() == Material.LEATHER_HELMET) {
as.getEquipment().setHelmet(null);
}
} }
} }
} }
/** private void spawnStarParticles(ArmorStand as) {
* Schaltet den Status um. Wird vom ArmorStandCommand aufgerufen. 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) { public void toggleDynamicStatus(ArmorStand as) {
if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE)) { if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE)) {
// Deaktivieren
as.getPersistentDataContainer().remove(npcKey); as.getPersistentDataContainer().remove(npcKey);
as.removeScoreboardTag("as_dynamic"); // Zur Sicherheit beides entfernen as.removeScoreboardTag("as_dynamic");
as.getEquipment().setItemInMainHand(null);
as.getEquipment().setItemInOffHand(null); as.getEquipment().setItemInOffHand(null);
as.getEquipment().setHelmet(null); as.setRightArmPose(EulerAngle.ZERO);
as.setLeftArmPose(EulerAngle.ZERO);
} else { } else {
// Aktivieren
as.getPersistentDataContainer().set(npcKey, PersistentDataType.BYTE, (byte) 1); as.getPersistentDataContainer().set(npcKey, PersistentDataType.BYTE, (byte) 1);
as.addScoreboardTag("as_dynamic"); as.addScoreboardTag("as_dynamic");
applyDynamicChanges(as, (as.getWorld().getTime() >= 13000 && as.getWorld().getTime() <= 23000));
// Sofortige visuelle Rückmeldung
int hour = LocalTime.now().getHour();
boolean isRaining = as.getWorld().hasStorm();
applyDynamicChanges(as, hour, isRaining);
} }
} }
@Override @Override
public void onDisable() { public void onDisable() {
// Nichts zu tun // Bereinigung falls nötig
} }
} }

View File

@@ -0,0 +1,312 @@
package de.nexuslobby.modules.ball;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import org.bukkit.util.Vector;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import java.util.Objects;
public class SoccerModule implements Module, Listener, CommandExecutor {
private ArmorStand ball;
private Location spawnLocation;
private long lastMoveTime;
private final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
private final String BALL_TAG = "nexusball_entity"; // Eindeutiges Tag zur Identifizierung
private final String BALL_NAME = "§x§N§e§x§u§s§B§a§l§l"; // Zusätzliche Erkennung
@Override
public String getName() { return "Soccer"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("nexuslobby") != null) {
Objects.requireNonNull(NexusLobby.getInstance().getCommand("nexuslobby")).setExecutor(this);
}
loadConfigLocation();
// AGGRESSIVES MEHRFACHES CLEANUP-SYSTEM
// 1. Sofort beim Enable
removeAllOldBallsGlobal();
// 2. Nach 0.5 Sekunden
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 10L);
// 3. Nach 1 Sekunde
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 20L);
// 4. Nach 2 Sekunden - cleanup und dann spawnen
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
removeAllOldBallsGlobal();
spawnBall();
}, 40L);
// 5. Nach 3 Sekunden - finales Cleanup für hartnäckige Duplikate
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 60L);
// 6. Nach 5 Sekunden - letzter Check
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 100L);
// Haupt-Physik & Anti-Klon Tick
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
// ANTI-KLON-SYSTEM: Prüfe die Umgebung des Spawns auf illegale Kopien
if (spawnLocation != null && spawnLocation.getWorld() != null) {
for (Entity entity : spawnLocation.getWorld().getNearbyEntities(spawnLocation, 50, 50, 50)) {
if (entity instanceof ArmorStand stand) {
// Wenn der ArmorStand unser Tag hat, aber nicht unsere aktive Instanz ist -> Löschen
if (stand.getScoreboardTags().contains(BALL_TAG)) {
if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) {
stand.remove();
}
}
}
}
}
if (ball == null || !ball.isValid()) return;
Vector vel = ball.getVelocity();
double speed = vel.length();
handleWallBounce(vel);
handleParticles(speed);
// Dribbel-Logik
for (Entity nearby : ball.getNearbyEntities(0.7, 0.5, 0.7)) {
if (nearby instanceof Player p) {
Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector());
if (direction.lengthSquared() > 0) {
direction.normalize();
direction.setY(0.12);
ball.setVelocity(direction.multiply(0.35));
}
lastMoveTime = System.currentTimeMillis();
}
}
// Automatischer Respawn bei Inaktivität oder Void
long delay = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000;
if (System.currentTimeMillis() - lastMoveTime > delay || ball.getLocation().getY() < -5) {
respawnBall();
}
}, 1L, 1L);
}
/**
* Scannt ALLE Welten nach Entities mit dem BALL_TAG und entfernt sie.
* Nutzt mehrere Erkennungsmethoden für maximale Sicherheit.
*/
private void removeAllOldBallsGlobal() {
int removed = 0;
// Alle Welten durchsuchen
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand stand) {
boolean shouldRemove = false;
// Methode 1: Tag-basiert
if (stand.getScoreboardTags().contains(BALL_TAG)) {
shouldRemove = true;
}
// Methode 2: Name-basiert (falls Tags verloren gehen)
if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) {
shouldRemove = true;
}
// Methode 3: Kopf-Textur-basiert (prüfe ob es ein Soccer-Ball Kopf ist)
if (stand.getEquipment() != null && stand.getEquipment().getHelmet() != null) {
ItemStack helmet = stand.getEquipment().getHelmet();
if (helmet.getType() == Material.PLAYER_HEAD && helmet.hasItemMeta()) {
SkullMeta meta = (SkullMeta) helmet.getItemMeta();
if (meta.hasOwner() && meta.getOwnerProfile() != null) {
PlayerProfile profile = meta.getOwnerProfile();
if (profile.getName() != null && profile.getName().equals("SoccerBall")) {
shouldRemove = true;
}
}
}
}
// Methode 4: Position-basiert - Entferne alle unsichtbaren, kleinen ArmorStands in der Nähe des Spawns
if (spawnLocation != null && spawnLocation.getWorld() != null &&
stand.getWorld().equals(spawnLocation.getWorld()) &&
stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) {
double distance = stand.getLocation().distance(spawnLocation);
// Wenn innerhalb von 5 Blöcken vom Spawn und hat einen Kopf
if (distance < 5.0 && stand.getEquipment() != null &&
stand.getEquipment().getHelmet() != null &&
stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) {
shouldRemove = true;
}
}
// Nur entfernen wenn es NICHT unsere aktuelle Ball-Instanz ist
if (shouldRemove && (ball == null || !stand.getUniqueId().equals(ball.getUniqueId()))) {
stand.remove();
removed++;
}
}
}
}
if (removed > 0) {
Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt.");
}
}
private void handleWallBounce(Vector vel) {
if (vel.lengthSquared() < 0.001) return;
Location loc = ball.getLocation();
Block nextX = loc.clone().add(vel.getX() * 1.3, 0.5, 0).getBlock();
Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * 1.3).getBlock();
boolean bounced = false;
if (nextX.getType().isSolid()) { vel.setX(-vel.getX() * 0.75); bounced = true; }
if (nextZ.getType().isSolid()) { vel.setZ(-vel.getZ() * 0.75); bounced = true; }
if (bounced) {
ball.setVelocity(vel);
ball.getWorld().playSound(ball.getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 1.3f);
}
}
private void handleParticles(double speed) {
if (speed < 0.05) return;
Location loc = ball.getLocation().add(0, 0.2, 0);
World world = loc.getWorld();
if (world == null) return;
if (speed > 0.85) world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0);
else if (speed > 0.45) world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08);
else world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
}
private void spawnBall() {
if (spawnLocation == null || (ball != null && ball.isValid())) return;
// Ball direkt auf dem Boden spawnen (keine Y-Offset Erhöhung)
Location spawnLoc = spawnLocation.clone();
ball = (ArmorStand) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.ARMOR_STAND);
ball.setInvisible(true);
ball.setGravity(true);
ball.setBasePlate(false);
ball.setSmall(true);
ball.setInvulnerable(false);
ball.setArms(false);
ball.setCustomNameVisible(false);
// Setze Custom Name für zusätzliche Erkennung (unsichtbar für Spieler)
ball.setCustomName(BALL_NAME);
// Deaktiviert das Speichern in der Welt-Datei
ball.setPersistent(false);
// Markiert den Ball für das Cleanup-System
ball.addScoreboardTag(BALL_TAG);
ItemStack ballHead = getSoccerHead();
if (ball.getEquipment() != null) ball.getEquipment().setHelmet(ballHead);
lastMoveTime = System.currentTimeMillis();
}
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(), "SoccerBall");
try {
profile.getTextures().setSkin(new URL(TEXTURE_URL));
} catch (MalformedURLException ignored) {}
meta.setOwnerProfile(profile);
head.setItemMeta(meta);
return head;
}
public void respawnBall() {
if (ball != null) {
ball.remove();
ball = null;
}
removeAllOldBallsGlobal();
spawnBall();
}
@EventHandler
public void onBallPunch(EntityDamageByEntityEvent event) {
if (event.getEntity().equals(ball)) {
event.setCancelled(true);
if (event.getDamager() instanceof Player p) {
Vector shootDir = p.getLocation().getDirection();
if (shootDir.lengthSquared() > 0) {
shootDir.normalize().multiply(1.35).setY(0.38);
ball.setVelocity(shootDir);
}
ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f);
lastMoveTime = System.currentTimeMillis();
}
}
}
@EventHandler
public void onBallInteract(PlayerInteractAtEntityEvent event) {
if (event.getRightClicked().equals(ball)) event.setCancelled(true);
}
private void loadConfigLocation() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
if (config.contains("ball.spawn")) spawnLocation = config.getLocation("ball.spawn");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player p) || !p.hasPermission("nexuslobby.admin")) return true;
if (args.length >= 2 && args[0].equalsIgnoreCase("ball")) {
if (args[1].equalsIgnoreCase("setspawn")) {
spawnLocation = p.getLocation();
NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation);
NexusLobby.getInstance().saveConfig();
respawnBall();
p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt. Cleanup ist aktiv!");
return true;
} else if (args[1].equalsIgnoreCase("respawn")) {
respawnBall();
p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt.");
return true;
}
}
return false;
}
@Override
public void onDisable() {
if (ball != null) ball.remove();
}
}

View File

@@ -0,0 +1,86 @@
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("§8[§6Nexus§8] §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]);
config.set("worldborder.type", "CIRCLE");
config.set("worldborder.center", p.getLocation());
config.set("worldborder.radius", radius);
config.set("worldborder.enabled", true);
p.sendMessage("§8[§6Nexus§8] §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("§8[§6Nexus§8] §cBitte markiere erst 2 Punkte mit der Portalwand!");
return true;
}
config.set("worldborder.type", "SQUARE");
config.set("worldborder.pos1", l1);
config.set("worldborder.pos2", l2);
config.set("worldborder.enabled", true);
p.sendMessage("§8[§6Nexus§8] §aViereckige Grenze erfolgreich gesetzt.");
}
case "disable" -> {
config.set("worldborder.enabled", false);
p.sendMessage("§8[§6Nexus§8] §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;
}
}

View File

@@ -0,0 +1,132 @@
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.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
/**
* Verwaltet die Lobby-Begrenzung (Kreis oder Rechteck).
* Speichert Daten in der Haupt-config.yml unter 'worldborder'.
*/
public class BorderModule implements Module, Listener {
private String type;
private Location pos1, pos2, center;
private double radius;
private boolean enabled;
@Override
public String getName() { return "WorldBorder"; }
@Override
public void onEnable() {
reloadConfig();
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
}
@Override
public void onDisable() {
// Aufräumarbeiten falls nötig
}
/**
* Lädt die Border-Einstellungen aus der config.yml.
* Wird auch von NexusLobby.reloadPlugin() aufgerufen.
*/
public void reloadConfig() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
// Pfad in der config.yml: worldborder.enabled etc.
this.enabled = config.getBoolean("worldborder.enabled", false);
this.type = config.getString("worldborder.type", "CIRCLE");
this.radius = config.getDouble("worldborder.radius", 50.0);
// Locations laden
this.center = config.getLocation("worldborder.center");
this.pos1 = config.getLocation("worldborder.pos1");
this.pos2 = config.getLocation("worldborder.pos2");
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
if (!enabled || event.getTo() == null) return;
// Performance: Nur prüfen, wenn sich die 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();
// Admins und Spectators ignorieren
if (player.hasPermission("nexuslobby.admin") || player.getGameMode().name().equals("SPECTATOR")) return;
Location to = event.getTo();
boolean outside = false;
// --- Prüfung: Kreis-Border ---
if (type.equalsIgnoreCase("CIRCLE") && center != null) {
if (to.getWorld().equals(center.getWorld())) {
double distSq = Math.pow(to.getX() - center.getX(), 2) + Math.pow(to.getZ() - center.getZ(), 2);
if (distSq > Math.pow(radius, 2)) outside = true;
}
}
// --- Prüfung: Rechteck-Border (Square) ---
else if (type.equalsIgnoreCase("SQUARE") && pos1 != null && pos2 != null) {
if (to.getWorld().equals(pos1.getWorld())) {
double minX = Math.min(pos1.getX(), pos2.getX());
double maxX = Math.max(pos1.getX(), pos2.getX());
double minZ = Math.min(pos1.getZ(), pos2.getZ());
double maxZ = Math.max(pos1.getZ(), pos2.getZ());
if (to.getX() < minX || to.getX() > maxX || to.getZ() < minZ || to.getZ() > maxZ) {
outside = true;
}
}
}
// --- Aktion wenn außerhalb ---
if (outside) {
Location spawnLocation = getMainSpawnLocation();
if (spawnLocation != null) {
// Sofortiger Teleport zum definierten Spawn
player.teleport(spawnLocation);
// Feedback an den Spieler
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f);
player.sendMessage("§8[§6Nexus§8] §cDu hast den Lobby-Bereich verlassen!");
}
}
}
/**
* Holt den zentralen 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"));
}
}
// Fallback falls kein Spawn gesetzt ist
return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0).getSpawnLocation();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ public class HoloCommand implements CommandExecutor {
return true; return true;
} }
module.removeHologram(args[1]); module.removeHologram(args[1]);
player.sendMessage("§8[§6Nexus§8] §cHologramm §e" + args[1] + " §ageloescht."); player.sendMessage("§8[§6Nexus§8] §cHologramm §e" + args[1] + " §agelöscht.");
} else { } else {
sendHelp(player); sendHelp(player);
} }

View File

@@ -7,7 +7,10 @@ import org.bukkit.Location;
import org.bukkit.World; 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.Entity;
import org.bukkit.entity.Interaction;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerChangedWorldEvent;
@@ -38,6 +41,7 @@ public class HologramModule implements Module, Listener {
loadHolograms(); loadHolograms();
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
// Render-Task: Prüft alle 5 Ticks Sichtbarkeit und Placeholder-Updates
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
holograms.values().forEach(h -> h.renderForPlayer(player)); holograms.values().forEach(h -> h.renderForPlayer(player));
@@ -51,12 +55,21 @@ public class HologramModule implements Module, Listener {
} }
private void loadHolograms() { private void loadHolograms() {
// Vorherige Instanzen säubern
holograms.values().forEach(NexusHologram::removeAll); holograms.values().forEach(NexusHologram::removeAll);
holograms.clear(); holograms.clear();
for (String id : config.getKeys(false)) { for (String id : config.getKeys(false)) {
World world = Bukkit.getWorld(config.getString(id + ".world", "world")); String worldName = config.getString(id + ".world");
if (worldName == null) continue;
World world = Bukkit.getWorld(worldName);
if (world == null) continue; if (world == null) continue;
Location loc = new Location(world, config.getDouble(id + ".x"), config.getDouble(id + ".y"), config.getDouble(id + ".z"));
Location loc = new Location(world,
config.getDouble(id + ".x"),
config.getDouble(id + ".y"),
config.getDouble(id + ".z"));
List<String> pages; List<String> pages;
if (config.isList(id + ".text")) { if (config.isList(id + ".text")) {
@@ -72,7 +85,9 @@ public class HologramModule implements Module, Listener {
@EventHandler @EventHandler
public void onInteract(PlayerInteractEntityEvent event) { public void onInteract(PlayerInteractEntityEvent event) {
// Wir prüfen, ob auf ein Interaction-Entity geklickt wurde // Nur auf Interaction-Entities reagieren (Hologramm-Hitboxen)
if (!(event.getRightClicked() instanceof Interaction)) return;
for (NexusHologram holo : holograms.values()) { for (NexusHologram holo : holograms.values()) {
if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) { if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) {
holo.nextPage(event.getPlayer()); holo.nextPage(event.getPlayer());
@@ -93,25 +108,32 @@ public class HologramModule implements Module, Listener {
@EventHandler @EventHandler
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
// Cleanup alter Entities für den Joiner // Cleanup alter Entity-Reste, die eventuell noch in der Welt schweben
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
event.getPlayer().getWorld().getEntities().forEach(entity -> { Player p = event.getPlayer();
for (Entity entity : p.getWorld().getEntities()) {
if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) { if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) {
if (!entity.getCustomName().endsWith("_" + event.getPlayer().getName())) { // Wenn das Hologramm nicht exakt für diesen Spieler benannt ist -> verstecken
event.getPlayer().hideEntity(NexusLobby.getInstance(), entity); if (!entity.getCustomName().endsWith("_" + p.getName())) {
p.hideEntity(NexusLobby.getInstance(), entity);
} }
} }
}); }
}, 5L); }, 10L);
} }
public void createHologram(String id, Location loc, List<String> pages) { 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 + ".world", loc.getWorld().getName());
config.set(id + ".x", loc.getX()); config.set(id + ".x", loc.getX());
config.set(id + ".y", loc.getY()); config.set(id + ".y", loc.getY());
config.set(id + ".z", loc.getZ()); config.set(id + ".z", loc.getZ());
config.set(id + ".text", pages); config.set(id + ".text", pages);
try { config.save(file); } catch (IOException e) { e.printStackTrace(); } saveHoloConfig();
NexusHologram holo = new NexusHologram(id, loc, pages); NexusHologram holo = new NexusHologram(id, loc, pages);
holograms.put(id, holo); holograms.put(id, holo);
@@ -119,12 +141,34 @@ public class HologramModule implements Module, Listener {
public void removeHologram(String id) { public void removeHologram(String id) {
NexusHologram holo = holograms.remove(id); NexusHologram holo = holograms.remove(id);
if (holo != null) holo.removeAll(); 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); config.set(id, null);
try { config.save(file); } catch (IOException e) { e.printStackTrace(); } saveHoloConfig();
} }
public Set<String> getHologramIds() { return config.getKeys(false); } private void saveHoloConfig() {
try {
config.save(file);
} catch (IOException e) {
NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!");
e.printStackTrace();
}
}
/**
* WICHTIG: Diese Methode wird vom LobbyTabCompleter benötigt!
* @return Set aller registrierten Hologramm-IDs
*/
public Set<String> getHologramIds() {
return holograms.keySet();
}
@Override @Override
public void onDisable() { public void onDisable() {

View File

@@ -45,8 +45,9 @@ public class NexusHologram {
} }
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), 0); int pageIdx = currentPage.getOrDefault(player.getUniqueId(), 0);
String rawText = pages.get(pageIdx); if (pageIdx >= pages.size()) pageIdx = 0;
String rawText = pages.get(pageIdx);
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
rawText = PlaceholderAPI.setPlaceholders(player, rawText); rawText = PlaceholderAPI.setPlaceholders(player, rawText);
} }
@@ -55,7 +56,8 @@ public class NexusHologram {
TextDisplay display = playerEntities.get(player.getUniqueId()); TextDisplay display = playerEntities.get(player.getUniqueId());
if (display == null || !display.isValid()) { if (display == null || !display.isValid()) {
// Text erstellen // Text-Display erstellen (Hier lassen wir den Namen zur internen Identifikation,
// aber schalten ihn strikt unsichtbar)
display = location.getWorld().spawn(location, TextDisplay.class, entity -> { display = location.getWorld().spawn(location, TextDisplay.class, entity -> {
entity.setCustomName("nexus_h_" + id + "_" + player.getName()); entity.setCustomName("nexus_h_" + id + "_" + player.getName());
entity.setCustomNameVisible(false); entity.setCustomNameVisible(false);
@@ -66,20 +68,21 @@ public class NexusHologram {
entity.setInvulnerable(true); entity.setInvulnerable(true);
}); });
// Interaction Entity für Klick-Erkennung (Hitbox) // 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 -> { Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> {
entity.setInteractionWidth(2.0f); entity.setInteractionWidth(2.5f);
entity.setInteractionHeight(2.0f); entity.setInteractionHeight(2.0f);
entity.setCustomNameVisible(false);
entity.setPersistent(false); entity.setPersistent(false);
}); });
final TextDisplay finalDisplay = display; // Nur für den Zielspieler sichtbar machen
final Interaction finalInteract = interact;
for (Player other : Bukkit.getOnlinePlayers()) { for (Player other : Bukkit.getOnlinePlayers()) {
if (!other.equals(player)) { if (!other.getUniqueId().equals(player.getUniqueId())) {
other.hideEntity(NexusLobby.getInstance(), finalDisplay); other.hideEntity(NexusLobby.getInstance(), display);
other.hideEntity(NexusLobby.getInstance(), finalInteract); other.hideEntity(NexusLobby.getInstance(), interact);
} }
} }
@@ -105,11 +108,11 @@ public class NexusHologram {
playerInteractions.values().forEach(Interaction::remove); playerInteractions.values().forEach(Interaction::remove);
playerEntities.clear(); playerEntities.clear();
playerInteractions.clear(); playerInteractions.clear();
currentPage.clear();
} }
public boolean isInteractionEntity(UUID entityId) { 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)); return playerInteractions.values().stream().anyMatch(i -> i.getUniqueId().equals(entityId));
} }
public String getId() { return id; }
} }

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,40 @@ 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 java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PortalCommand implements CommandExecutor { public class PortalCommand implements CommandExecutor {
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;
} }
// 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
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
@@ -23,13 +49,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 +70,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 +81,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 +93,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 +121,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!");

View File

@@ -31,7 +31,7 @@ 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, Markierungen und den globalen Grenzschutz.
*/ */
public class PortalManager implements Module, Listener { public class PortalManager implements Module, Listener {
@@ -42,6 +42,11 @@ public class PortalManager implements Module, Listener {
private final NamespacedKey wandKey; private final NamespacedKey wandKey;
private BukkitTask particleTask; private BukkitTask particleTask;
// Boundary Cache
private Location borderMin;
private Location borderMax;
private boolean borderEnabled = false;
public PortalManager(NexusLobby plugin) { public PortalManager(NexusLobby plugin) {
this.plugin = plugin; this.plugin = plugin;
this.wandKey = new NamespacedKey(plugin, "nexuslobby_portal_wand"); this.wandKey = new NamespacedKey(plugin, "nexuslobby_portal_wand");
@@ -55,9 +60,10 @@ public class PortalManager implements Module, Listener {
@Override @Override
public void onEnable() { public void onEnable() {
loadPortals(); loadPortals();
loadBorderSettings();
Bukkit.getPluginManager().registerEvents(this, plugin); Bukkit.getPluginManager().registerEvents(this, plugin);
startParticleTask(); startParticleTask();
plugin.getLogger().info("PortalManager geladen."); plugin.getLogger().info("PortalManager vollständig geladen.");
} }
@Override @Override
@@ -70,15 +76,25 @@ public class PortalManager implements Module, Listener {
plugin.getLogger().info("PortalManager deaktiviert."); plugin.getLogger().info("PortalManager deaktiviert.");
} }
/** public void loadBorderSettings() {
* Gibt alle Namen der aktuell geladenen Portale zurück. if (plugin.getConfig().contains("border.pos1") && plugin.getConfig().contains("border.pos2")) {
* Wird vom LobbyTabCompleter genutzt. Location p1 = plugin.getConfig().getLocation("border.pos1");
*/ Location p2 = plugin.getConfig().getLocation("border.pos2");
if (p1 != null && p2 != null) {
this.borderMin = getMinLocation(p1, p2);
this.borderMax = getMaxLocation(p1, p2);
this.borderEnabled = true;
}
} else {
this.borderEnabled = false;
}
}
public Set<String> getPortalNames() { public Set<String> getPortalNames() {
return portals.keySet(); return portals.keySet();
} }
// --- 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 +103,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 +112,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 +126,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("§e[Nexus] Kleiner Bereich erkannt (Portal-Größe)");
p.sendMessage("§fBefehl: §b/portal create <Name> <server|world>");
} else {
p.sendMessage("§6[Nexus] Großer Bereich erkannt (WorldBorder-Größe)");
p.sendMessage("§fBefehl: §a/border square");
}
p.sendMessage("§7§m----------------------------------");
} }
} }
} }
@@ -140,14 +172,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;
@@ -262,31 +288,7 @@ public class PortalManager implements Module, Listener {
return true; return true;
} }
// --- Particles --- // --- Movement / Teleport / Boundary 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 +298,22 @@ public class PortalManager implements Module, Listener {
} }
Player player = event.getPlayer(); Player player = event.getPlayer();
Location loc = player.getLocation(); Location loc = event.getTo();
// 1. Grenzschutz (Boundary Protection)
if (borderEnabled && !player.hasPermission("nexuslobby.border.bypass")) {
if (!isWithinBorder(loc)) {
Location spawn = getMainSpawnLocation();
if (spawn != null) {
player.teleport(spawn);
player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 0.5f);
player.sendMessage("§c§lHEY! §7Du hast den erlaubten Bereich verlassen.");
}
return;
}
}
// 2. Portal Logik
for (Portal portal : portals.values()) { for (Portal portal : portals.values()) {
if (portal.getPos1() == null || portal.getPos2() == null) continue; if (portal.getPos1() == null || portal.getPos2() == null) continue;
if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue; if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue;
@@ -313,11 +329,18 @@ public class PortalManager implements Module, Listener {
} }
} }
private boolean isWithinBorder(Location loc) {
if (borderMin == null || borderMax == null) return true;
if (!loc.getWorld().equals(borderMin.getWorld())) return true;
return loc.getX() >= borderMin.getX() && loc.getX() <= borderMax.getX() &&
loc.getY() >= borderMin.getY() && loc.getY() <= borderMax.getY() &&
loc.getZ() >= borderMin.getZ() && loc.getZ() <= borderMax.getZ();
}
private void executeTeleport(Player player, Portal portal) { private void executeTeleport(Player player, Portal portal) {
if ("SERVER".equalsIgnoreCase(portal.getType())) { if ("SERVER".equalsIgnoreCase(portal.getType())) {
String serverName = portal.getDestination(); String serverName = portal.getDestination();
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) {
@@ -335,7 +358,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 +374,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 +381,84 @@ 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 >= 6 ? 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());
e.printStackTrace(); 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();
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);
}
} }
} }

View File

@@ -0,0 +1,119 @@
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 java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.CompletableFuture;
public class ServerChecker {
/**
* 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() {
// WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen
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);
}
/**
* Analysiert die Tags eines ArmorStands und aktualisiert den Namen basierend auf dem Serverstatus.
*/
private static void checkAndUpdateArmorStand(ArmorStand as) {
for (String tag : as.getScoreboardTags()) {
// Erwartetes Format: ascmd:slot1:slot2:type:command
if (tag.startsWith("ascmd:")) {
String[] parts = tag.split(":");
if (parts.length < 5) continue;
String type = parts[3].toLowerCase();
if (!type.equals("bungee")) continue;
String serverName = parts[4];
// Suche den passenden Key in der Config
String configKey = null;
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) {
for (String key : NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false)) {
if (key.equalsIgnoreCase(serverName)) {
configKey = key;
break;
}
}
}
if (configKey == null) continue;
String ip = NexusLobby.getInstance().getConfig().getString("servers." + configKey + ".ip");
int port = NexusLobby.getInstance().getConfig().getInt("servers." + configKey + ".port");
if (ip == null) continue;
// Der Ping selbst läuft asynchron weg vom Main-Thread
isOnline(ip, port).thenAccept(online -> {
// Zurück zum Main-Thread für die Änderung des ArmorStands
Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> {
if (!as.isValid()) return; // Sicherheitscheck falls AS gelöscht wurde
if (online) {
restoreName(as);
} else {
String offlineText = "§c● §lOFFLINE §c●";
if (!offlineText.equals(as.getCustomName())) {
as.setCustomName(offlineText);
as.setCustomNameVisible(true);
}
}
});
});
break;
}
}
}
/**
* Stellt den ursprünglichen Namen des ArmorStands wieder her.
* Nutzt den Tag "asname:NAME" als Backup.
*/
private static void restoreName(ArmorStand as) {
for (String t : as.getScoreboardTags()) {
if (t.startsWith("asname:")) {
String originalName = t.substring(7);
String translatedName = ChatColor.translateAlternateColorCodes('&', originalName);
if (!translatedName.equals(as.getCustomName())) {
as.setCustomName(translatedName);
as.setCustomNameVisible(true);
}
return;
}
}
}
}

View File

@@ -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,30 @@ 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
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) {}
} }
} }
} }
@@ -154,7 +176,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 +184,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 +193,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");

View File

@@ -11,6 +11,14 @@ spawn:
yaw: 0.0 # Blickrichtung yaw: 0.0 # Blickrichtung
pitch: 0.0 # Blickrichtung pitch: 0.0 # Blickrichtung
worldborder:
enabled: true
type: "SQUARE" # oder "CIRCLE"
radius: 50.0
center:
pos1:
pos2:
# --- Lobby Einstellungen --- # --- Lobby Einstellungen ---
lobby: lobby:
allow-fly: false # Spieler dürfen fliegen allow-fly: false # Spieler dürfen fliegen
@@ -19,6 +27,16 @@ lobby:
default-gamemode: Adventure default-gamemode: Adventure
clear-inventory-on-join: true clear-inventory-on-join: true
# Mapping für den Server-Status-Ping der ArmorStands
# Der Name (z.B. survival) muss exakt dem Bungee-Servernamen entsprechen
servers:
survival:
ip: "127.0.0.1"
port: 25566
skyblock:
ip: "127.0.0.1"
port: 25567
# --- Tablist Einstellungen --- # --- Tablist Einstellungen ---
tablist: tablist:
enabled: true enabled: true
@@ -46,7 +64,7 @@ items:
portals: portals:
default-particle: "PORTAL" default-particle: "PORTAL"
portal-cooldown: 40 # Ticks, 2 Sekunden portal-cooldown: 40 # Ticks, 2 Sekunden
save-file: "portals.yml" # Datei im Plugin-Ordner save-file: "portals.yml"
# ----------------------------------------------------- # -----------------------------------------------------
# COMPASS MENU # COMPASS MENU
@@ -79,6 +97,13 @@ compass:
lore: lore:
- "&7Zeige was du kannst!" - "&7Zeige was du kannst!"
# -----------------------------------------------------
# PLAYER INSPECT (Statistiken per Rechtsklick)
# -----------------------------------------------------
player_inspect:
enabled: true
gui_title: "&8Statistiken von &6{PLAYER}"
# --- Suppressor / Global Chat Einstellungen --- # --- Suppressor / Global Chat Einstellungen ---
suppressor: suppressor:
enabled: true enabled: true
@@ -135,3 +160,19 @@ hider:
all: "&aAlle Spieler: &7Sichtbar" all: "&aAlle Spieler: &7Sichtbar"
# Anzeigename des Items und Nachricht, wenn alle versteckt sind # Anzeigename des Items und Nachricht, wenn alle versteckt sind
none: "&cKeine Spieler: &7Versteckt" none: "&cKeine Spieler: &7Versteckt"
# -----------------------------------------------------
# BALL / SOCCER EINSTELLUNGEN
# -----------------------------------------------------
ball:
enabled: true
# Der Spawnpunkt wird automatisch über /nexus ball setspawn hier gespeichert
spawn:
world: "world"
x: 10.5
y: 65.0
z: 10.5
yaw: 0.0
pitch: 0.0
# Zeit in Sekunden, bis der Ball bei Inaktivität respawnt
respawn_delay: 60

View File

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

View File

@@ -1,9 +1,9 @@
name: NexusLobby name: NexusLobby
main: de.nexuslobby.NexusLobby main: de.nexuslobby.NexusLobby
version: "1.0.4" version: "1.1.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:
@@ -38,15 +38,18 @@ commands:
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]
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: holo:
description: Verwalte Lobby Hologramme (Text-Displays) description: Verwalte Lobby Hologramme (Text-Displays)
usage: /holo <create|delete> <id> [text] usage: /holo <create|delete> <id> [text]
@@ -59,6 +62,28 @@ commands:
description: Verwalte und teste die Cinematic Intro Tour description: Verwalte und teste die Cinematic Intro Tour
usage: /intro <add|clear|start> usage: /intro <add|clear|start>
permission: nexuslobby.admin 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:
@@ -74,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, Intro 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
@@ -83,14 +108,23 @@ 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 default: op
nexuslobby.armorstand.dynamic: nexuslobby.armorstand.dynamic:
description: Erlaubt das Markieren von dynamischen NPCs description: Erlaubt das Markieren von dynamischen NPCs
default: op default: op
nexuslobby.armorstand.lookat:
description: Erlaubt das Aktivieren des Blickkontakts bei NPCs
default: op
nexuslobby.hologram: nexuslobby.hologram:
description: Erlaubt das Erstellen von Text-Display Hologrammen description: Erlaubt das Erstellen von Text-Display Hologrammen
default: op default: op
nexuslobby.mapart: nexuslobby.mapart:
description: Erlaubt das Erstellen von Map-Art Bildern description: Erlaubt das Erstellen von Map-Art Bildern
default: op default: op
nexuslobby.silentjoin:
description: Versteckt die Join-Nachricht für den Spieler (Silent Join)
default: op
nexuslobby.parkour.admin:
description: Erlaubt das Setzen der unsichtbaren Parkour-Punkte (Start, Ziel, Checkpoints)
default: op