13 Commits
1.0.3 ... main

Author SHA1 Message Date
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
fa6c79aa53 README.md aktualisiert 2026-01-23 11:40:28 +00:00
27b9563a45 Update from Git Manager GUI 2026-01-23 12:17:49 +01:00
96973d44e5 Upload pom.xml via GUI 2026-01-23 11:17:49 +00:00
6630621ba1 Upload pom.xml via GUI 2026-01-23 09:43:54 +00:00
5f2c05d85e Update from Git Manager GUI 2026-01-23 10:43:53 +01:00
32 changed files with 3765 additions and 728 deletions

198
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 personlichen Gebrauch gestattet - Die Nutzung ist ausschließlich für den persönlichen Gebrauch gestattet.
- Die Weitergabe, Verbreitung oder Veroffentlichung des Plugins ist **strengstens untersagt** - Die Weitergabe, Verbreitung oder Veröffentlichung des Plugins ist **strengstens untersagt**.
- Jegliche Anderung, Modifikation oder Dekompilierung des Codes ist **verboten** - Jegliche Änderung, Modifikation oder Dekompilierung des Codes ist **verboten**.
- Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden - Das Plugin darf nicht verkauft, vermietet oder anderweitig kommerziell genutzt werden.
- Eine Weitergabe an Dritte ist ohne ausdruckliche schriftliche Genehmigung nicht gestattet - Eine Weitergabe an Dritte ist ohne ausdrückliche schriftliche Genehmigung nicht gestattet.
Bei Verstoss gegen diese Bedingungen behalten wir uns rechtliche Schritte vor. Bei Verstoß gegen diese Bedingungen behalten wir uns rechtliche Schritte vor.
--- ---
## Features ## Features
### Lobby-Management ### 🤖 High-End NPC & ArmorStand System
- **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 fur nicht-berechtigte Spieler - **Maintenance** - Vollständiger Wartungsmodus mit Whitelist-Funktion.
- **Lobby-Schutz** - Verhindert Griefing und unerwunschte 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** - Unterdruckung fur globalen Chat
---
## Installation
### Voraussetzungen
- Paper/Spigot Server 1.21 oder hoher
- Java 21 oder hoher
### Optionale Abhangigkeiten
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) - Fur Platzhalter-Support
- [LuckPerms](https://luckperms.net/) - Fur Berechtigungsverwaltung
### Schritte
1. Lade `NexusLobby.jar` herunter
2. Kopiere die JAR in den `plugins/` Ordner deines Servers
3. Starte den Server neu
4. Konfiguriere das Plugin in `plugins/NexusLobby/`
--- ---
@@ -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 Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.
- [Issues](../../../issues) - Bug-Reports und Feature-Requests
---
**Copyright (c) 2024 - Alle Rechte vorbehalten**
Die unbefugte Vervielfaltigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.

373
example-conversations.yml Normal file
View File

@@ -0,0 +1,373 @@
# ==========================================
# example Conversations Configuration
# ==========================================
conversations:
test:
dialogue:
- '&eNPC 1: &7Hallo!'
- '&aNPC 2: &7Hi, wie geht es dir?'
- '&eNPC 1: &7Bestens, danke!'
parkour_begruessung:
dialogue:
- '&6&lTrainer &8» &fWillkommen bei &e&lViper-Network&f!'
- '&6&lTrainer &8» &fHast du das Zeug zum &aParkour-Meister&f?'
- '&6&lTrainer &8» &eKlick mich an&f, um den Parkour zu starten!'
owner_suche:
dialogue:
- '&dTimmy: &7Mami, Mami! Siehst du irgendwo den Owner?'
- '&5Sarah: &7Geduld, Kleiner. Er hat sicher viel zu tun.'
- '&dTimmy: &7Aber ich wollte ihm doch meinen neuen Teddy zeigen!'
- '&5Sarah: &7Vielleicht läuft er uns später über den Weg. Komm, wir schauen mal.'
- '&dTimmy: &7Oder er ist unsichtbar... wie ein Geist! Buuuuh!'
- '&5Sarah: &7Haha, möglich ist es. Bleib jetzt schön an meiner Hand.'
baum_szene:
dialogue:
- '&aLotte: &7Schau mal nach oben! Ich kann fliegen! Ich bin eine Fledermaus!'
- '&bSchmidt: &7Äh... du stehst auf einer Birke. Das ist nicht fliegen, das ist Klettern.'
- '&aLotte: &7Gleich breite ich meine Flügel aus und gleite über den Nexus!'
- '&bSchmidt: &7Nichts wie weg hier... die ist ja völlig verrückt geworden.'
- '&aLotte: &7Geronimoooooo!'
- '&bSchmidt: &7Ich hab dich gewarnt. Ich geh lieber schnell weiter.'
fussball_spiel:
dialogue:
- '&eLeon: &7Na komm schon, versuch doch mir den Ball abzunehmen!'
- '&6Finn: &7Träum weiter, Leon! Gleich hab ich ihn und dann zieh ich ab.'
- '&eLeon: &7Tor! Hast du das gesehen? Voll in den Winkel!'
- '&6Finn: &7Das zählt nicht, ich hab gerade meine Schuhe gebunden!'
- '&eLeon: &7Ausreden, nichts als Ausreden. 1:0 für mich!'
- '&6Finn: &7Na warte, jetzt zeig ich dir meinen Spezialschuss! Hol den Ball raus!'
1_support_hilfe:
dialogue:
- "&bTim: &7Hey! Sag mal, hast du heute schon den neuen Bug-Melder ausprobiert?"
- "&fNoah: &7Noch nicht wirklich. Ist das dieses neue System mit /report?"
- "&bTim: &7Genau! Es hilft dem Team extrem, wenn wir Fehler direkt dokumentieren."
- "&fNoah: &7Und was passiert, wenn ich einen Fehler melde? Bekomme ich eine Belohnung?"
- "&bTim: &7Die größte Belohnung ist ein flüssiges Spiel für alle, aber manchmal gibt es kleine Überraschungen."
- "&fNoah: &7Das klingt fair. Ich werde die Augen offen halten, während ich in der Farmwelt bin."
- "&bTim: &7Super! Vergiß nicht, so viele Details wie möglich anzugeben. Screenshots helfen auch!"
- "&fNoah: &7Mache ich! Danke für den Hinweis, Tim. Wir sehen uns später im Chat."
2_shop_gespraech:
dialogue:
- "&6Bob: &7Guten Tag! Suchen Sie heute etwas Bestimmtes für Ihre Ausrüstung?"
- "&fFinn: &7Ich brauche dringend Verstärkung für meine Spitzhacke. Hast du Effizienz-Bücher?"
- "&6Bob: &7Da haben Sie Glück! Ich habe gerade eine Lieferung aus der Bibliothek erhalten."
- "&fFinn: &7Das ist perfekt. Aber der Preis ist seit letzter Woche ganz schön gestiegen, oder?"
- "&6Bob: &7Die Nachfrage ist riesig, seitdem die neue Mine eröffnet wurde. Alle graben wie verrückt."
- "&fFinn: &7Na gut, Qualität hat eben ihren Preis. Ich nehme zwei Stück von den Büchern."
- "&6Bob: &7Gute Entscheidung! Damit werden Sie durch den Stein gleiten wie durch Butter."
- "&fFinn: &7Das hoffe ich doch. Vielen Dank und einen erfolgreichen Handel noch!"
3_discord_talk:
dialogue:
- "&9Marie: &7Es ist manchmal echt ruhig hier am Spawn, findest du nicht auch?"
- "&fPia: &7Ja, ein bisschen mehr Action könnte nicht schaden."
- "&9Marie: &7Wenn du Lust auf Events hast, solltest du mal bei uns im Discord vorbeischauen!"
- "&fPia: &7Oh, ich wusste gar nicht, dass ihr dort auch Wettbewerbe macht."
- "&9Marie: &7Und wie! Gestern gab es ein riesiges Quiz über die Geschichte von Viper-Network."
- "&fPia: &7Cool! Wie komme ich dort am schnellsten hin?"
- "&9Marie: &7Gib einfach /discord ein, um den Einladungslink zu erhalten."
- "&fPia: &7Werde ich direkt mal machen. Danke, Marie!"
4_angler_latein:
dialogue:
- "&3Uwe: &7Petri Heil, Fritz! Wie sieht die Ausbeute heute aus?"
- "&bFritz: &7Ach Uwe, frag lieber nicht. Nur ein paar kleine Fische und jede Menge Müll."
- "&3Uwe: &7Hast du es mal mit dem neuen Glücksköder probiert, den es im Shop gibt?"
- "&bFritz: &7Ich halte nichts von diesem modernen Kram. Eine gute Rute ist alles, was man braucht."
- "&3Uwe: &7Tja, dann darfst du dich nicht wundern, wenn ich die dicken Lachse rausziehe."
- "&bFritz: &7Vielleicht hast du heute einfach nur eine Glückssträhne. Morgen sieht das wieder anders aus."
- "&3Uwe: &7Wir werden sehen. Ich werfe meine Angel jetzt mal da drüben bei den Seerosen aus."
- "&bFritz: &7Viel Erfolg. Wenn du einen Schatz findest, sag mir Bescheid!"
5_baeckerei_duft:
dialogue:
- "&6Hans: &7Frisches Brot! Knusprige Kekse! Greifen Sie zu, solange sie warm sind!"
- "&fMarc: &7Dieser Duft zieht einen ja magisch an. Was ist heute die Spezialität?"
- "&6Hans: &7Heute habe ich Kürbiskuchen mit einer Prise Zimt im Angebot."
- "&fMarc: &7Das klingt herrlich. Ich nehme direkt zwei Stücke für meine Reise."
- "&6Hans: &7Sehr gerne. Planst du eine längere Tour in die Wildnis?"
- "&fMarc: &7Ja, ich will die Berge im Norden erkunden. Da brauche ich ordentlich Proviant."
- "&6Hans: &7Dann nimm noch diese frischen Äpfel mit. Die geben Kraft für den Aufstieg."
- "&fMarc: &7Danke Hans! Dein Service ist wirklich unschlagbar."
6_wegweiser_spawn:
dialogue:
- "&fKevin: &7Entschuldigung, wissen Sie zufällig, wo das Portal zum Nether steht?"
- "&7Thorsten: &7Sicher! Du musst am großen Brunnen links abbiegen und dann geradeaus."
- "&fKevin: &7Oh, da bin ich wohl vorhin genau in die falsche Richtung gelaufen."
- "&7Thorsten: &7Keine Sorge, das passiert vielen Neulingen hier. Der Spawn ist recht verwinkelt."
- "&fKevin: &7Gibt es dort eigentlich eine Schutzmauer oder ist es dort gefährlich?"
- "&7Thorsten: &7Hinter dem Portal fängt die Gefahr direkt an. Sei also gut ausgerüstet!"
- "&fKevin: &7Danke für die Warnung. Ich werde vorsichtig sein."
- "&7Thorsten: &7Viel Erfolg beim Farmen! Und komm heil zurück."
7_geheimtreffen:
dialogue:
- "&8Ralle: &7Ede, hast du die Kiste an den vereinbarten Ort gebracht?"
- "&7Ede: &7Ja, sie liegt gut versteckt unter den Fässern am Hafen."
- "&8Ralle: &7Hat dich jemand beobachtet? Die Wachen sind heute besonders aufmerksam."
- "&7Ede: &7Ich bin durch die Kanalisation geschlichen. Da unten trifft man nur Ratten."
- "&8Ralle: &7Hervorragend. Wenn die Übergabe heute Nacht klappt, sind wir reich."
- "&7Ede: &7Ich traue der Sache trotzdem nicht ganz. Der Käufer wirkt zwielichtig."
- "&8Ralle: &7In unserem Geschäft ist niemand vertrauenswürdig. Pass einfach auf dich auf."
- "&7Ede: &7Mache ich. Ich gebe dir ein Zeichen, wenn alles erledigt ist."
8_wetter_plausch:
dialogue:
- "&fBernd: &7Schau dir diesen Himmel an. Endlich mal kein Regen über dem Spawn."
- "&fBeate: &7Stimmt, das ist das perfekte Wetter, um an meinem Garten weiterzuarbeiten."
- "&fBernd: &7Was pflanzt du dieses Mal an? Wieder diese seltenen Tulpen?"
- "&fBeate: &7Genau! Ich habe Samen aus fernen Landen bekommen, die nachts leuchten sollen."
- "&fBernd: &7Das wird bestimmt toll aussehen. Vielleicht schaue ich später mal vorbei."
- "&fBeate: &7Gerne! Aber bring keine dreckigen Schuhe mit, ich habe gerade erst gepflastert."
- "&fBernd: &7Haha, versprochen! Ich putze sie vorher extra ab."
- "&fBeate: &7Na, dann bin ich ja beruhigt. Bis später dann!"
9_parkour_training:
dialogue:
- "&eNico: &7Patrick, dieser Sprung über die Lava macht mich fertig. Ich schaffe es nicht!"
- "&aPatrick: &7Du springst viel zu hektisch ab. Du musst den Moment kurz vor der Kante abwarten."
- "&eNico: &7Aber dann habe ich das Gefühl, dass ich einfach in die Lava falle!"
- "&aPatrick: &7Das ist reine Kopfsache. Vertraue deinem Speed und drück die Leertaste später."
- "&eNico: &7Okay... ich versuche es nochmal. Diesmal ganz konzentriert."
- "&aPatrick: &7Das war schon viel besser! Du hast die Kante fast erreicht."
- "&eNico: &7Ja! Ich glaube, ich habe das Gefühl für die Distanz jetzt endlich raus."
- "&aPatrick: &7Sehr gut. Wiederholung ist der Schlüssel zum Erfolg. Mach weiter so!"
10_schmiede_talk:
dialogue:
- "&eBen: &7Nick, schau dir mal diese Kerbe in meinem Schwert an. Kannst du das richten?"
- "&7Nick: &7Oha, das sieht nach einem harten Kampf gegen ein Wither-Skelett aus."
- "&eBen: &7Treffer! Die Viecher sind zäher, als ich dachte. Mein Schild hat auch gelitten."
- "&7Nick: &7Lass die Sachen hier. Ich werde sie im Ofen neu härten und polieren."
- "&eBen: &7Wie lange wird das dauern? Ich wollte eigentlich heute noch mal los."
- "&7Nick: &7Gib mir eine Stunde. Handarbeit braucht eben ihre Zeit, wenn es halten soll."
- "&eBen: &7In Ordnung, ich warte so lange beim Bäcker und hol mir eine Stärkung."
- "&7Nick: &7Guter Plan. Dein Schwert wird danach schärfer sein als je zuvor."
11_wachen_bericht:
dialogue:
- "&7Hardy: &7Soldat! Wie ist der Status am westlichen Außenposten?"
- "&7Willi: &7Alles ruhig, Sir! Wir hatten heute nur ein paar Wanderer, die nach dem Weg fragten."
- "&7Hardy: &7Keine Sichtungen von Griefern oder verdächtigen Gestalten?"
- "&7Willi: &7Negativ. Die Patrouillen wurden wie befohlen verdoppelt."
- "&7Hardy: &7Gut so. Wir dürfen uns keine Nachlässigkeit erlauben, besonders nachts nicht."
- "&7Willi: &7Ich habe die Männer angewiesen, die Fackeln früher anzuzünden."
- "&7Hardy: &7Sehr aufmerksam. Melden Sie sich sofort, wenn sich die Lage ändert."
- "&7Willi: &8*salutiert* &7Zu Befehl, Sir! Wir halten die Stellung."
12_haustier_drama:
dialogue:
- "&dSusi: &7Gerd! Mein kleiner Bello ist schon wieder aus dem Gehege ausgebüxt!"
- "&7Gerd: &7Nicht schon wieder, Susi. Hast du das Tor vielleicht nicht richtig verriegelt?"
- "&dSusi: &7Ich war mir so sicher! Er ist bestimmt wieder dem Postboten hinterhergelaufen."
- "&7Gerd: &7Ich habe vorhin ein Bellen beim Marktplatz gehört. Schau doch mal dort nach."
- "&dSusi: &7Hoffentlich hat er keinen Stand umgeworfen, sonst krieg ich wieder Ärger."
- "&7Gerd: &7Er ist ein guter Hund, er will doch nur spielen. Keine Sorge."
- "&dSusi: &7Ich lauf schnell los! Danke für den Tipp, Gerd."
- "&7Gerd: &7Viel Erfolg! Und nimm diesmal eine stabilere Leine mit."
13_willkommens_guide:
dialogue:
- "&6Gabi: &7Hallo! Du siehst so aus, als wärst du gerade erst hier angekommen."
- "&fNoah: &7Stimmt genau. Ich bin noch ein wenig überwältigt von der Größe des Spawns."
- "&6Gabi: &7Keine Angst, das legt sich schnell. Hast du schon deine Start-Ausrüstung abgeholt?"
- "&fNoah: &7Noch nicht. Wo finde ich die denn? Ich stehe hier nur mit leeren Händen."
- "&6Gabi: &7Folge einfach dem Pfad zum blauen Gebäude. Dort gibt es alles für den Anfang."
- "&fNoah: &7Das ist ja super! Und was mache ich danach am besten?"
- "&6Gabi: &7Ich empfehle dir, die Farmwelt zu besuchen, um deine ersten Rohstoffe zu sammeln."
- "&fNoah: &7Vielen Dank für die nette Begrüßung, Gabi! Ich lege direkt los."
14_architektur_lob:
dialogue:
- "&fSven: &7Wahnsinn, wie die Admins dieses riesige Schloss da hinten gebaut haben."
- "&fSarah: &7Ich habe gehört, dass ein ganzes Team über einen Monat daran gearbeitet hat."
- "&fSven: &7Die Details an den Fenstern sind echt beeindruckend. Ob das alles Handarbeit war?"
- "&fSarah: &7Ganz sicher! Hier im Viper-Network wird viel Wert auf Ästhetik gelegt."
- "&fSven: &7Das motiviert mich direkt, an meiner eigenen kleinen Hütte weiterzubauen."
- "&fSarah: &7Das ist der Geist! Vielleicht wird dein Haus ja auch mal eine Sehenswürdigkeit."
- "&fSven: &7Haha, bis dahin ist es noch ein weiter Weg. Aber Träumen darf man ja."
- "&fSarah: &7Genau! Ich helfe dir später gerne beim Designen, wenn du magst."
15_ausblicks_genuss:
dialogue:
- "&aMax: &7Schau mal Peter! Von hier oben sieht der ganze Spawn aus wie ein Spielzeugdorf."
- "&7Peter: &7Pass auf, Max! Geh nicht zu nah an die Kante, das Geländer ist dort locker."
- "&aMax: &7Keine Sorge, ich habe meine Federfall-Stiefel an. Mir passiert nichts."
- "&7Peter: &7Trotzdem! Wir wollen hier keinen Rettungseinsatz provozieren."
- "&aMax: &7Guck mal, da unten läuft gerade der Owner über den Platz!"
- "&7Peter: &7Echt? Den sieht man ja selten. Er scheint wohl nach dem Rechten zu schauen."
- "&aMax: &7Ich winke ihm mal zu! Vielleicht sieht er uns ja hier oben."
- "&7Peter: &7Komm jetzt lieber runter, die Besichtigungstour geht gleich weiter."
16_markt_handel:
dialogue:
- "&6Silas: &7Edelsteine! Seltene Erze! Nur heute zum halben Preis!"
- "&fGuy: &7Halber Preis? Das sagen Sie doch jeden zweiten Tag, Silas."
- "&6Silas: &7Aber heute meine ich es ernst! Die Minen sind randvoll und ich brauche Platz."
- "&fGuy: &7Na gut, zeigen Sie mal her. Haben Sie auch Diamanten in dieser Qualität?"
- "&6Silas: &7Nur die feinsten Stücke. Schauen Sie, wie dieser hier im Licht funkelt."
- "&fGuy: &7Nicht schlecht... was möchten Sie für diesen Stapel Smaragde haben?"
- "&6Silas: &7Sagen wir 500 Coins und wir haben ein Geschäft. Ein echtes Schnäppchen!"
- "&fGuy: &7Einverstanden. Aber legen Sie mir noch diesen kleinen Amethysten dazu."
17_bauprojekt_planung:
dialogue:
- "&eArno: &7Ali, hast du die Pläne für die neue Arena schon fertig gezeichnet?"
- "&7Ali: &7Fast, Chef. Ich überlege noch, ob wir das Dach aus Glas oder Stein bauen sollen."
- "&eArno: &7Glas wäre spektakulär für die Zuschauer, aber Stein ist viel stabiler."
- "&7Ali: &7Vielleicht eine Kombination aus beidem? Ein gläsernes Zentrum mit Steinpfeilern."
- "&eArno: &7Das klingt nach einer modernen Lösung. Aber denk an die Kosten für das viele Glas."
- "&7Ali: &7Wir könnten Sand in der Wüste farmen und ihn direkt vor Ort schmelzen."
- "&eArno: &7Gute Idee. Dann fangen wir morgen mit den Fundamenten an. Bereite alles vor!"
- "&7Ali: &7Geht klar! Ich werde die Arbeiter informieren und die Schaufeln schärfen."
18_mutprobe_brunnen:
dialogue:
- "&cRolf: &7Ich wette mit dir, dass ich vom Turm direkt in den Brunnen springen kann!"
- "&7Ansgar: &7Lass das lieber, Rolf. Wenn du daneben springst, landest du im Lazarett."
- "&cRolf: &7Ach was, ich habe das schon hundertmal in anderen Welten gemacht."
- "&7Ansgar: &7Hier ist die Schwerkraft aber tückisch. Und der Brunnen ist gar nicht so tief."
- "&cRolf: &7Guck einfach zu und lerne! Gleich gibt es einen riesigen Platscher."
- "&7Ansgar: &7Ich halte mir lieber die Augen zu... ich kann das nicht mit ansehen."
- "&cRolf: &7Drei... zwei... eins... Geronimoooooo!"
- "&7Ansgar: &7Rolf? Lebst du noch? Oh Gott, er hat wirklich getroffen!"
19_spaziergang_talk:
dialogue:
- "&dRon: &7Ist es nicht herrlich, wie friedlich es hier am Abend ist?"
- "&dJule: &7Absolut. Die Lichter am Wegrand machen eine ganz tolle Stimmung."
- "&dRon: &7Ich bin froh, dass wir uns für diesen Server entschieden haben. Die Leute sind nett."
- "&dJule: &7Stimmt. Ich habe heute schon drei neue Freunde beim Farmen kennengelernt."
- "&dRon: &7Wollen wir morgen mal versuchen, gemeinsam eine große Gilde zu gründen?"
- "&dJule: &7Das wäre eine super Idee! Wir brauchen nur noch ein passendes Grundstück."
- "&dRon: &7Ich habe da schon einen Platz im Auge. Direkt am See im Osten."
- "&dJule: &7Perfekt. Ich freue mich schon darauf, mit dir zu bauen."
20_minenfunde_stolz:
dialogue:
- "&eGustav: &7Phil, du wirst nicht glauben, was ich heute in Schicht 12 gefunden habe!"
- "&7Phil: &7Lass mich raten... wieder nur Kohle und ein paar einsame Eisenbarren?"
- "&eGustav: &7Nein! Eine ganze Ader aus purem Gold, direkt neben einem unterirdischen See."
- "&7Phil: &7Wahnsinn! Hast du die Stelle gut abgesichert? Nicht dass andere sie finden."
- "&eGustav: &7Ich habe den Eingang mit Kies getarnt. Niemand wird dort so schnell graben."
- "&7Phil: &7Wir sollten heute Nacht zurückkehren und alles abbauen, bevor es jemand merkt."
- "&eGustav: &7Ganz genau. Bring deine beste Spitzhacke mit, wir haben viel Arbeit vor uns."
- "&7Phil: &7Ich bin bereit! Das wird unser großer Durchbruch, Gustav."
21_musik_begeisterung:
dialogue:
- "&bDennis: &7Hast du die neue Notenblock-Anlage gehört, die beim Spawn-Eingang steht?"
- "&fPia: &7Ja! Die Melodie ist so eingängig, ich bekomme sie gar nicht mehr aus dem Kopf."
- "&bDennis: &7Ich habe gehört, dass einer der Spieler die ganze Nacht daran programmiert hat."
- "&fPia: &7Echt? Es ist Wahnsinn, was man mit Redstone alles anstellen kann."
- "&bDennis: &7Ich versuche gerade, etwas Ähnliches für meine eigene Base zu bauen."
- "&fPia: &7Kannst du mir das dann mal zeigen? Ich liebe gute Musik im Spiel."
- "&bDennis: &7Klar! Sobald der Refrain steht, lade ich dich auf eine Hörprobe ein."
- "&fPia: &7Ich freue mich drauf! Musik macht das Bauen einfach viel angenehmer."
22_pferde_pflege:
dialogue:
- "&6Rico: &7Hey Sam, mein Hengst lahmt ein bisschen auf dem linken Vorderfuß."
- "&7Sam: &7Lass mich mal sehen. Vielleicht hat er sich einen Stein im Huf eingetreten."
- "&6Rico: &7Wir waren heute im steinigen Gelände unterwegs, das könnte gut sein."
- "&7Sam: &7Tatsächlich! Hier steckt ein kleiner Kieselstein fest. Ich hol ihn mal raus."
- "&6Rico: &7Ganz vorsichtig... er ist heute etwas schreckhaft wegen der Gewitter."
- "&7Sam: &7Schon erledigt. Er sollte jetzt wieder ganz normal laufen können."
- "&6Rico: &7Vielen Dank, Sam! Du hast wirklich ein Händchen für Tiere."
- "&7Sam: &7Immer wieder gerne. Reite ihn heute nicht mehr zu hart, er braucht Ruhe."
23_lehrer_warnung:
dialogue:
- "&9Lempel: &7Simon, was haben wir über das Graben in der Nähe von Lava gelernt?"
- "&7Simon: &7Man sollte immer einen Block Abstand halten und niemals direkt drüber stehen."
- "&9Lempel: &7Und trotzdem hast du heute fast deine ganze Ausrüstung im Feuer verloren!"
- "&7Simon: &7Ich dachte, ich wäre schnell genug, um das Erz zu schnappen..."
- "&9Lempel: &7Gier ist in der Mine dein größter Feind. Merkt euch das alle!"
- "&7Simon: &7Ich habe meine Lektion gelernt, Herr Lehrer. Sicherheit geht vor Profit."
- "&9Lempel: &7Das hoffe ich doch sehr. Wir wollen hier keine weiteren Unfälle sehen."
- "&7Simon: &7Ich werde ab jetzt immer einen Eimer Wasser griffbereit in der Hand halten."
24_party_vorbereitung:
dialogue:
- "&aPaul: &7Gerda, hast du schon die Einladungen für die Spawn-Party verschickt?"
- "&fGerda: &7Ja, alle Stammspieler sollten heute Post in ihrem Briefkasten haben."
- "&aPaul: &7Sehr gut. Ich kümmere mich um die Dekoration und die Feuerwerksraketen."
- "&fGerda: &7Vergiss nicht, auch ein paar vegetarische Snacks für die Alchemisten zu besorgen."
- "&aPaul: &7Guter Punkt! Ich werde ein paar goldene Karotten und Äpfel bereitstellen."
- "&fGerda: &7Das wird bestimmt die beste Party des Jahres. Ich bin schon ganz aufgeregt."
- "&aPaul: &7Ich auch! Hoffentlich spielt das Wetter mit und wir haben einen klaren Sternenhimmel."
- "&fGerda: &7Wenn nicht, spannen wir einfach ein großes Sonnensegel über den Platz."
25_wald_geraeusche:
dialogue:
- "&cZack: &7Hermann, hast du das Knacken da drüben im Dickicht auch gehört?"
- "&7Hermann: &7Bestimmt nur ein Wildschwein oder ein verspätetes Kaninchen, Zack."
- "&cZack: &7Das klang aber viel schwerfälliger. Fast wie ein riesiger Zombie!"
- "&7Hermann: &7Beruhig dich. Wir sind hier in der geschützten Zone, hier spawnt nichts."
- "&cZack: &7Vielleicht ist einer aus der Farmwelt entwischt und uns gefolgt?"
- "&7Hermann: &7Unmöglich. Die Portale sind für Monster absolut undurchlässig."
- "&cZack: &7Ich behalte den Wald trotzdem lieber im Auge. Sicher ist sicher."
- "&7Hermann: &7Wenn es dich beruhigt, patrouillieren wir eben eine Runde um den See."
26_feuerwerk_test:
dialogue:
- "&4Wulf: &7Paul, halte dir die Ohren zu! Ich teste jetzt die neue Schwarzpulver-Mischung."
- "&7Paul: &7Bist du sicher, dass der Sicherheitsabstand hier am Spawn ausreicht?"
- "&4Wulf: &7Klar! Das ist eine kontrollierte Verpuffung mit blauen Farbeffekten."
- "&7Paul: &7Das letzte Mal hast du das halbe Dach von der Schmiede versengt..."
- "&4Wulf: &7Das war ein Rechenfehler! Dieses Mal habe ich die Zündschnur verlängert."
- "&7Paul: &7Na gut... ich geh trotzdem mal lieber hinter diese Steinmauer dort."
- "&4Wulf: &7Achtung... Drei... zwei... eins... Zündung!"
- "&7Paul: &7Wow! Das war ja wunderschön! Die ganze Luft funkelt blau."
27_alchemie_versuch:
dialogue:
- "&5Silas: &7Tom, probier mal diesen Trank hier. Er sollte deine Sprungkraft verdoppeln."
- "&fTom: &7Bist du sicher? Die Flüssigkeit brodelt ganz schön heftig und ist giftgrün."
- "&5Silas: &7Das ist nur die Reaktion der Spinnenaugen mit dem Glowstone-Staub."
- "&fTom: &7Und es gibt garantiert keine Nebenwirkungen? Ich will nicht als Frosch enden."
- "&5Silas: &7Höchstens ein leichtes Kribbeln in den Zehen. Das ist völlig normal."
- "&fTom: &7Na gut, auf dein Risiko... *gluckgluck* ... hey, ich fühle mich plötzlich ganz leicht!"
- "&5Silas: &7Siehst du! Jetzt versuch mal, über diesen Zaun dort zu springen."
- "&fTom: &7Wahnsinn! Ich bin fast drei Blöcke hoch gesprungen! Das ist ja genial!"
28_versteckspiel_kinder:
dialogue:
- "&7Kalle: &798... 99... 100! Alles was nicht bis drei auf den Bäumen ist, wird gesucht!"
- "&7Kira: &8(Flüstert) &7Hoffentlich findet er mich hier hinter der großen Kiste nicht."
- "&7Kalle: &7Ich höre doch jemanden kichern! Bist du etwa im Blumenbeet, Kira?"
- "&7Kira: &8(Leise) &7Mist, ich muss ganz still sein und darf mich nicht bewegen."
- "&7Kalle: &7Hier ist wohl niemand... vielleicht ist sie ja in Richtung Brunnen gelaufen."
- "&7Kira: &8(Atmet auf) &7Puh, das war knapp. Er ist direkt an mir vorbeigegangen."
- "&7Kalle: &7Hab dich! Deine Schleife hat aus der Lücke zwischen den Kisten rausgeschaut!"
- "&7Kira: &7Och Menno! Ich dachte, das Versteck wäre diesmal wirklich perfekt."
29_erfolgs_freude:
dialogue:
- "&aSandro: &7Finn, ich habe es endlich geschafft! Ich habe 1000 Spielstunden erreicht!"
- "&7Finn: &7Was? Das ist ja eine unglaubliche Leistung. Du bist ein echter Veteran."
- "&aSandro: &7Es fühlt sich toll an. Ich habe so viele Freunde hier gefunden und so viel gebaut."
- "&7Finn: &7Erinnerst du dich noch an unseren ersten Tag, als wir vor dem Creeper geflohen sind?"
- "&aSandro: &7Haha, ja! Wir hatten nichts außer zwei Holzschwertern und einer Menge Hoffnung."
- "&7Finn: &7Und jetzt stehst du hier in voller Pracht. Du hast dir den Titel redlich verdient."
- "&aSandro: &7Danke Finn. Ohne deine Hilfe bei der Farm wäre ich nie so weit gekommen."
- "&7Finn: &7Wir sind ein Team. Auf die nächsten 1000 Stunden, Partner!"
30_reise_start:
dialogue:
- "&fWilli: &7Thorsten, meine Taschen sind gepackt. Es ist Zeit, dem Spawn Lebewohl zu sagen."
- "&7Thorsten: &7Pass gut auf dich auf da draußen. Die Wildnis verzeiht keine Fehler."
- "&fWilli: &7Ich habe genug Vorräte und eine gute Karte dabei. Ich werde vorsichtig sein."
- "&7Thorsten: &7Hier, nimm noch diesen Kompass. Er wird dir immer den Weg nach Hause zeigen."
- "&fWilli: &7Danke, mein Freund. Das bedeutet mir viel. Ich werde euch vermissen."
- "&7Thorsten: &7Wir halten hier am Spawn die Stellung, bis du mit deinen Schätzen zurückkehrst."
- "&fWilli: &7Ich freue mich schon auf den Tag, an dem ich euch meine Entdeckungen zeige."
- "&7Thorsten: &7Gute Reise, Willi! Möge das Glück dich auf all deinen Pfaden begleiten."

View File

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

View File

@@ -13,14 +13,14 @@ 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; // Hinzugefügt
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.utils.VoidProtection; import de.nexuslobby.modules.hologram.HologramModule;
import de.nexuslobby.utils.DoubleJump; import de.nexuslobby.modules.mapart.MapArtModule;
import de.nexuslobby.utils.PlayerHider; import de.nexuslobby.modules.intro.IntroModule;
import de.nexuslobby.utils.MaintenanceListener; import de.nexuslobby.modules.border.BorderModule;
import de.nexuslobby.utils.ConfigUpdater; import de.nexuslobby.utils.*;
import de.nexuslobby.utils.UpdateChecker;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.ComponentBuilder;
@@ -28,18 +28,25 @@ 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.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,33 +57,58 @@ public class NexusLobby extends JavaPlugin implements Listener {
private LobbySettingsModule lobbySettingsModule; private LobbySettingsModule lobbySettingsModule;
private ItemsModule itemsModule; private ItemsModule itemsModule;
private GadgetModule gadgetModule; private GadgetModule gadgetModule;
private HologramModule hologramModule;
private DynamicArmorStandModule dynamicArmorStandModule;
private MapArtModule mapArtModule;
private IntroModule introModule;
private BorderModule borderModule;
private ConversationManager conversationManager;
private File visualsFile; private File visualsFile;
private FileConfiguration visualsConfig; private FileConfiguration visualsConfig;
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;
}
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
// Erst Config initialisieren
initCustomConfigs(); initCustomConfigs();
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
moduleManager = new ModuleManager(this); moduleManager = new ModuleManager(this);
this.conversationManager = new ConversationManager(this);
ArmorStandGUI.init(); ArmorStandGUI.init();
registerModules(); registerModules();
moduleManager.enableAll(); moduleManager.enableAll();
registerListeners(); registerListeners();
// --- STATUS CHECKER START ---
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.");
@@ -85,7 +117,86 @@ 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 startAutoConversationTimer() {
new BukkitRunnable() {
@Override
public void run() {
if (conversationManager == null) return;
for (World world : Bukkit.getWorlds()) {
for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) {
if (as.getScoreboardTags().stream().anyMatch(tag -> tag.startsWith("conv_id:"))) {
boolean playerNearby = false;
for (Entity nearby : as.getNearbyEntities(30, 30, 30)) {
if (nearby instanceof Player) {
playerNearby = true;
break;
}
}
if (playerNearby) {
String dialogId = null;
String partnerUUID = null;
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("conv_id:")) dialogId = tag.split(":")[1];
if (tag.startsWith("conv_partner:")) partnerUUID = tag.split(":")[1];
}
if (dialogId != null && partnerUUID != null) {
try {
UUID partnerId = UUID.fromString(partnerUUID);
conversationManager.playConversation(as.getUniqueId(), partnerId, dialogId);
} catch (Exception ignored) {}
}
}
}
}
}
}
}.runTaskTimer(this, 20L * 30, 20L * 90);
}
public void reloadPlugin() {
getLogger().info("Plugin Reload wird gestartet...");
Bukkit.getScheduler().cancelTasks(this);
if (moduleManager != null) {
moduleManager.disableAll();
}
reloadConfig();
visualsConfig = null;
reloadVisualsConfig();
Config.load();
if (conversationManager != null) {
conversationManager.setupFile();
}
if (borderModule != null) {
borderModule.reloadConfig();
}
if (portalManager != null) {
portalManager.loadPortals();
}
ArmorStandGUI.init();
if (moduleManager != null) {
moduleManager.enableAll();
}
ServerChecker.startGlobalChecker();
new ArmorStandLookAtModule();
startAutoConversationTimer();
getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen.");
} }
private void checkUpdates() { private void checkUpdates() {
@@ -95,7 +206,6 @@ public class NexusLobby extends JavaPlugin implements Listener {
this.latestVersion = version; this.latestVersion = version;
getLogger().warning("===================================================="); getLogger().warning("====================================================");
getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version); getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version);
getLogger().warning("Autor: M_Viper");
getLogger().warning("===================================================="); getLogger().warning("====================================================");
} else { } else {
getLogger().info("NexusLobby ist aktuell (v" + version + ")."); getLogger().info("NexusLobby ist aktuell (v" + version + ").");
@@ -103,30 +213,6 @@ public class NexusLobby extends JavaPlugin implements Listener {
}); });
} }
public void reloadPlugin() {
getLogger().info("Plugin Reload wird gestartet...");
if (moduleManager != null) {
moduleManager.disableAll();
}
reloadConfig();
visualsConfig = null;
reloadVisualsConfig();
Config.load();
if (portalManager != null) {
portalManager.loadPortals();
}
ArmorStandGUI.init();
if (moduleManager != null) {
moduleManager.enableAll();
}
getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen.");
}
private void registerModules() { private void registerModules() {
moduleManager.registerModule(new ProtectionModule()); moduleManager.registerModule(new ProtectionModule());
moduleManager.registerModule(new ScoreboardModule()); moduleManager.registerModule(new ScoreboardModule());
@@ -137,17 +223,34 @@ public class NexusLobby extends JavaPlugin implements Listener {
this.gadgetModule = new GadgetModule(); this.gadgetModule = new GadgetModule();
moduleManager.registerModule(this.gadgetModule); moduleManager.registerModule(this.gadgetModule);
this.hologramModule = new HologramModule();
moduleManager.registerModule(this.hologramModule);
this.dynamicArmorStandModule = new DynamicArmorStandModule();
moduleManager.registerModule(this.dynamicArmorStandModule);
moduleManager.registerModule(new ArmorStandStatusModule());
this.mapArtModule = new MapArtModule();
moduleManager.registerModule(this.mapArtModule);
this.introModule = new IntroModule();
moduleManager.registerModule(this.introModule);
this.borderModule = new BorderModule();
moduleManager.registerModule(this.borderModule);
moduleManager.registerModule(new SecurityModule()); moduleManager.registerModule(new SecurityModule());
moduleManager.registerModule(new BossBarModule()); moduleManager.registerModule(new BossBarModule());
moduleManager.registerModule(new ActionBarModule()); moduleManager.registerModule(new ActionBarModule());
lobbySettingsModule = new LobbySettingsModule(); this.lobbySettingsModule = new LobbySettingsModule();
moduleManager.registerModule(lobbySettingsModule); moduleManager.registerModule(lobbySettingsModule);
tablistModule = new TablistModule(); this.tablistModule = new TablistModule();
moduleManager.registerModule(tablistModule); moduleManager.registerModule(tablistModule);
portalManager = new PortalManager(this); this.portalManager = new PortalManager(this);
moduleManager.registerModule(portalManager); moduleManager.registerModule(portalManager);
} }
@@ -166,6 +269,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);
@@ -186,56 +291,57 @@ 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);
// Nur speichern wenn sie fehlt
saveResource("config.yml", false);
} else {
// WICHTIG: ConfigUpdater für config.yml deaktiviert, da er Sektionen nicht korrekt erkennt!
// ConfigUpdater.updateConfig("config.yml");
}
// Einfaches Laden reicht völlig aus
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;
} }
@@ -243,22 +349,26 @@ public class NexusLobby extends JavaPlugin implements Listener {
public void onDisable() { public void onDisable() {
getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
if (moduleManager != null) moduleManager.disableAll(); if (moduleManager != null) moduleManager.disableAll();
getLogger().info("NexusLobby disabled"); getLogger().info("NexusLobby deaktiviert.");
} }
private void registerCommands() { private void registerCommands() {
LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager); LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule);
NexusLobbyCommand nexusCommand = new NexusLobbyCommand();
PluginCommand portalCmd = this.getCommand("portal"); if (getCommand("portal") != null) {
if (portalCmd != null) { getCommand("portal").setExecutor(new PortalCommand(portalManager));
portalCmd.setExecutor(new PortalCommand(portalManager)); getCommand("portal").setTabCompleter(tabCompleter);
portalCmd.setTabCompleter(tabCompleter);
} }
PluginCommand maintenanceCmd = this.getCommand("maintenance"); if (getCommand("holo") != null) {
if (maintenanceCmd != null) { getCommand("holo").setExecutor(new HoloCommand(hologramModule));
maintenanceCmd.setExecutor(new MaintenanceCommand()); getCommand("holo").setTabCompleter(tabCompleter);
maintenanceCmd.setTabCompleter(tabCompleter); }
if (getCommand("maintenance") != null) {
getCommand("maintenance").setExecutor(new MaintenanceCommand());
getCommand("maintenance").setTabCompleter(tabCompleter);
} }
if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this)); if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this));
@@ -275,16 +385,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);
}
if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter);
if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter);
if (getCommand("border") != null) {
getCommand("border").setExecutor(new BorderCommand());
getCommand("border").setTabCompleter(tabCompleter);
} }
} }
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; }
@@ -294,13 +416,18 @@ 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";
return null; return null;
} }
} }
public ModuleManager getModuleManager() { return moduleManager; } public ModuleManager getModuleManager() { return moduleManager; }
public PortalManager getPortalManager() { return portalManager; } // Hinzugefügt/Wiederhergestellt
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 DynamicArmorStandModule getDynamicArmorStandModule() { return dynamicArmorStandModule; }
public MapArtModule getMapArtModule() { return mapArtModule; } // Wiederhergestellt
} }

View File

@@ -1,9 +1,12 @@
package de.nexuslobby.commands; package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.portal.PortalManager; import de.nexuslobby.modules.portal.PortalManager;
import de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -13,87 +16,143 @@ import java.util.stream.Collectors;
public class LobbyTabCompleter implements TabCompleter { public class LobbyTabCompleter implements TabCompleter {
private final PortalManager portalManager; private final PortalManager portalManager;
private final HologramModule hologramModule;
public LobbyTabCompleter(PortalManager portalManager) { public LobbyTabCompleter(PortalManager portalManager, HologramModule hologramModule) {
this.portalManager = portalManager; this.portalManager = portalManager;
this.hologramModule = hologramModule;
} }
@Override @Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
List<String> suggestions = new ArrayList<>(); List<String> suggestions = new ArrayList<>();
String cmdName = command.getName().toLowerCase();
// --- NexusLobby Hauptbefehl --- // --- NexusLobby Hauptbefehl (/nexus) ---
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"));
}
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"));
}
}
}
// --- Hologram Befehl ---
else if (cmdName.equals("holo")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("create", "delete"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) {
if (hologramModule != null) {
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")) {
suggestions.addAll(portalManager.getPortalNames()); if (portalManager != null) {
suggestions.addAll(portalManager.getPortalNames());
}
} }
} }
// --- NexusTools (ehemals astools) --- // --- MapArt ---
else if (command.getName().equalsIgnoreCase("nexustools") || command.getName().equalsIgnoreCase("astools") || command.getName().equalsIgnoreCase("nt")) { else if (cmdName.equals("mapart")) {
if (args.length == 1) { if (args.length == 1) {
suggestions.add("reload"); suggestions.add("https://");
} } else if (args.length == 2) {
} suggestions.addAll(Arrays.asList("1x1", "3x2", "6x4", "8x5"));
// --- NexusCmd (ehemals ascmd) ---
else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ascmd") || command.getName().equalsIgnoreCase("ncmd")) {
if (args.length == 1) {
suggestions.add("name"); // NEU
suggestions.add("list");
suggestions.add("add");
suggestions.add("remove");
}
// Vorschläge für: /nexuscmd name <Text>
else if (args.length == 2 && args[0].equalsIgnoreCase("name")) {
suggestions.add("none");
suggestions.add("<Text>");
}
// Vorschläge für: /nexuscmd add <delay> <cooldown> <type>
else if (args.length == 2 && args[0].equalsIgnoreCase("add")) {
suggestions.add("0");
}
else if (args.length == 3 && args[0].equalsIgnoreCase("add")) {
suggestions.add("0");
}
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
suggestions.add("player");
suggestions.add("console");
suggestions.add("bungee");
}
else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) {
suggestions.add("<Servername>");
} }
} }
// Filtert die Liste basierend auf dem, was der Spieler bereits getippt hat // --- Intro System ---
else if (cmdName.equals("intro")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("add", "clear", "start"));
}
}
// --- WorldBorder ---
else if (cmdName.equals("border")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("circle", "square", "disable"));
} else if (args.length == 2 && args[0].equalsIgnoreCase("circle")) {
suggestions.addAll(Arrays.asList("10", "25", "50", "100", "250"));
}
}
// --- NexusCmd (ArmorStand Commands & Dialoge) ---
else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd")) {
if (args.length == 1) {
suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv"));
}
else if (args.length == 2) {
if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("name")) {
suggestions.addAll(Arrays.asList("<Anzeigename>", "none"));
} else if (args[0].equalsIgnoreCase("conv")) {
// NEU: unlink hinzugefügt
suggestions.addAll(Arrays.asList("select1", "select2", "link", "unlink", "start"));
} else if (args[0].equalsIgnoreCase("remove")) {
suggestions.add("all");
}
}
else if (args.length == 3) {
if (args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"));
} else if (args[0].equalsIgnoreCase("conv")) {
if (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link")) {
if (NexusLobby.getInstance().getConversationManager() != null) {
suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds());
}
}
}
}
else if (args.length == 4 && args[0].equalsIgnoreCase("add")) {
suggestions.addAll(Arrays.asList("bungee", "console", "player"));
}
else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) {
if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) {
suggestions.addAll(NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false));
}
}
}
// --- ArmorStandTools (/astools) ---
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"));
}
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 (für Case-Insensitivity und Teilübereinstimmung)
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,150 @@ package de.nexuslobby.commands;
import de.nexuslobby.NexusLobby; import de.nexuslobby.NexusLobby;
import de.nexuslobby.modules.ScoreboardModule; import de.nexuslobby.modules.ScoreboardModule;
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.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class NexusLobbyCommand implements CommandExecutor { public class NexusLobbyCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// Sub-Befehl: /nexus sb <on|off|admin|spieler> if (!(sender instanceof Player player)) {
if (args.length >= 2 && args[0].equalsIgnoreCase("sb")) { sender.sendMessage("§cDieser Befehl ist nur für Spieler!");
if (!(sender instanceof Player)) { return true;
sender.sendMessage("§cDieser Befehl ist nur für Spieler!"); }
return true;
}
Player player = (Player) sender;
ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class);
if (sbModule == null) { // --- SPAWN BEFEHL ---
player.sendMessage("§cDas Scoreboard-Modul ist aktuell deaktiviert."); if (command.getName().equalsIgnoreCase("spawn")) {
return true; FileConfiguration config = NexusLobby.getInstance().getConfig();
} if (config.contains("spawn.world")) {
Location loc = getSpawnFromConfig(config);
String sub = args[1].toLowerCase(); if (loc != null) {
switch (sub) { player.teleport(loc);
case "on": player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.2f);
sbModule.setVisibility(player, true); player.sendMessage("§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert.");
break; } else {
case "off": player.sendMessage("§cFehler: Die Spawn-Welt existiert nicht.");
sbModule.setVisibility(player, false); }
break; } else {
case "admin": player.sendMessage("§cEs wurde noch kein Spawn gesetzt.");
if (!player.hasPermission("nexuslobby.scoreboard.admin")) {
player.sendMessage("§cKeine Rechte für den Admin-Modus.");
return true;
}
sbModule.setAdminMode(player, true);
break;
case "spieler":
if (!player.hasPermission("nexuslobby.scoreboard.admin")) {
player.sendMessage("§cKeine Rechte für den Admin-Modus.");
return true;
}
sbModule.setAdminMode(player, false);
break;
default:
player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>");
break;
} }
return true; return true;
} }
// Sub-Befehl: /nexus reload // --- HAUPTBEFEHL ARGUMENTE ---
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { if (args.length == 0) {
if (!sender.hasPermission("nexuslobby.admin")) { sendInfo(player);
sender.sendMessage("§cDu hast keine Berechtigung für diesen Befehl.");
return true;
}
sender.sendMessage("§7[§6NexusLobby§7] §eKonfigurationen und Module werden neu geladen...");
NexusLobby.getInstance().reloadPlugin();
sender.sendMessage("§7[§6NexusLobby§7] §aDas Plugin wurde erfolgreich neu geladen!");
return true; return true;
} }
// Standard-Info switch (args[0].toLowerCase()) {
String version = NexusLobby.getInstance().getDescription().getVersion(); case "reload":
sender.sendMessage("§8§m--------------------------------------"); if (!player.hasPermission("nexuslobby.admin")) {
sender.sendMessage("§6§lNexusLobby §7- Informationen"); player.sendMessage("§cKeine Berechtigung.");
sender.sendMessage(""); return true;
sender.sendMessage("§ePlugin Name: §fNexusLobby"); }
sender.sendMessage("§eVersion: §f" + version);
sender.sendMessage("§eAutor: §fM_Viper"); // Aufruf der Reload-Methode in der Hauptklasse
sender.sendMessage(""); NexusLobby.getInstance().reloadPlugin();
sender.sendMessage("§6Befehle:");
sender.sendMessage("§f/nexus reload §7- Plugin neu laden"); player.sendMessage("§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!");
sender.sendMessage("§f/nexus sb <on|off> §7- Scoreboard schalten"); player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f);
if (sender.hasPermission("nexuslobby.scoreboard.admin")) { break;
sender.sendMessage("§f/nexus sb <admin|spieler> §7- Modus wechseln");
case "setspawn":
if (!player.hasPermission("nexuslobby.admin")) {
player.sendMessage("§cKeine Berechtigung.");
return true;
}
Location loc = player.getLocation();
FileConfiguration config = NexusLobby.getInstance().getConfig();
config.set("spawn.world", loc.getWorld().getName());
config.set("spawn.x", loc.getX());
config.set("spawn.y", loc.getY());
config.set("spawn.z", loc.getZ());
config.set("spawn.yaw", (double) loc.getYaw());
config.set("spawn.pitch", (double) loc.getPitch());
NexusLobby.getInstance().saveConfig();
player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!");
break;
case "silentjoin":
if (!player.hasPermission("nexuslobby.silentjoin")) {
player.sendMessage("§cKeine Berechtigung.");
return true;
}
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;
default:
sendInfo(player);
break;
} }
sender.sendMessage("§8§m--------------------------------------");
return true; return true;
} }
private void handleScoreboard(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage("§cBenutzung: /nexus sb <on|off|admin|spieler>");
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;
}
}
private Location getSpawnFromConfig(FileConfiguration config) {
World world = Bukkit.getWorld(config.getString("spawn.world", "world"));
if (world == null) return null;
return new Location(world,
config.getDouble("spawn.x"), config.getDouble("spawn.y"), config.getDouble("spawn.z"),
(float) config.getDouble("spawn.yaw"), (float) config.getDouble("spawn.pitch"));
}
private void sendInfo(Player player) {
player.sendMessage("§8§m--------------------------------------");
player.sendMessage("§6§lNexusLobby §7- Informationen");
player.sendMessage("");
player.sendMessage("§f/spawn §7- Zum Spawn teleportieren");
player.sendMessage("§f/nexus setspawn §7- Spawn setzen");
player.sendMessage("§f/nexus silentjoin §7- Join-Nachricht umschalten");
player.sendMessage("§f/nexus sb <on|off> §7- Scoreboard");
player.sendMessage("§f/nexus reload §7- Konfiguration 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,7 @@ 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;
public class ASTListener implements Listener { public class ASTListener implements Listener {
@@ -26,7 +25,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 +35,116 @@ public class ASTListener implements Listener {
return; return;
} }
// Normale Interaktion (Befehl ausführen) // --- 2. FALL: NORMALER KLICK -> Befehle ausführen (Bungee, etc.) ---
Set<String> tags = as.getScoreboardTags(); for (String tag : as.getScoreboardTags()) {
for (String tag : tags) {
if (tag.startsWith("ascmd:")) { if (tag.startsWith("ascmd:")) {
event.setCancelled(true); String[] parts = tag.split(":");
executeNexusCommand(p, tag); if (parts.length < 5) continue;
break;
String type = parts[3].toLowerCase();
String command = parts[4];
event.setCancelled(true); // Verhindert z.B. dass man Items klaut
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; // Nur den ersten gefundenen Befehl ausführen
} }
} }
} }
/**
* 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;
} }
ArmorStandTool tool = ArmorStandTool.get(item); ItemStack item = event.getCurrentItem();
if (tool != null) { if (item == null || item.getType() == Material.AIR) return;
tool.execute(as, p);
// GUI aktualisieren, falls noch offen (SET_NAME schließt es z.B.) if (title.equals("Nexus ArmorStand Editor")) {
if (p.getOpenInventory().getTitle().equals("Nexus ArmorStand Editor")) { ArmorStandTool tool = ArmorStandTool.get(item);
if (tool != null) {
tool.execute(as, p);
if (p.getOpenInventory().getTitle().equals(title)) {
new ArmorStandGUI(as, p);
}
}
}
else if (title.equals("Pose: Körperteil wählen")) {
if (item.getType() == Material.BARRIER) {
new ArmorStandGUI(as, p); new ArmorStandGUI(as, p);
return;
}
String targetPart = null;
switch (item.getType()) {
case PLAYER_HEAD -> targetPart = "HEAD_ROT";
case IRON_CHESTPLATE -> targetPart = "BODY_ROT";
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) {
private void executeNexusCommand(Player p, String tag) { ArmorStandPoseGUI.openPartSelectionMenu(p, as);
try { return;
String[] parts = tag.split(":", 3);
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(); }
} 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;
private void sendToBungee(Player p, String server) { EulerAngle oldPose = ArmorStandPoseGUI.getAngleForPart(as, tool);
ByteArrayDataOutput out = ByteStreams.newDataOutput(); EulerAngle newPose = oldPose;
out.writeUTF("Connect");
out.writeUTF(server); if (item.getType() == Material.RED_DYE) {
p.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", out.toByteArray()); 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,26 @@
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.UUID;
import java.util.Set;
/**
* ArmorStandCmdExecutor - Erweiterte Steuerung für ArmorStand-Interaktionen.
* Nutzt Raytracing für präzise Auswahl und permanentes Dialog-Linking sowie Status-Backup.
*/
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,74 +34,175 @@ 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 true; return sendConvHelp(p);
} }
switch (args[1].toLowerCase()) {
case "select1":
case "select2":
ArmorStand target = getTargetArmorStand(p);
if (target == null) {
p.sendMessage(prefix + "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!");
return true;
}
boolean isFirst = args[1].equalsIgnoreCase("select1");
String metaKey = isFirst ? "conv_npc1" : "conv_npc2";
UUID targetUUID = target.getUniqueId();
p.setMetadata(metaKey, new FixedMetadataValue(NexusLobby.getInstance(), targetUUID.toString()));
p.sendMessage(prefix + "§aNPC §e" + (isFirst ? "1" : "2") + " §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 erst beide 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());
String dialogId = args[2];
Entity entity1 = Bukkit.getEntity(id1);
if (entity1 instanceof ArmorStand as1) {
as1.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_partner:") || tag.startsWith("conv_id:"));
as1.addScoreboardTag("conv_partner:" + id2.toString());
as1.addScoreboardTag("conv_id:" + dialogId);
NexusLobby.getInstance().getConversationManager().saveLink(id1, id2, dialogId);
p.sendMessage(prefix + "§a§lDauerhafte Verknüpfung erstellt!");
p.spawnParticle(Particle.HAPPY_VILLAGER, as1.getLocation().add(0, 1.5, 0), 20, 0.4, 0.4, 0.4, 0.1);
} 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
targetUnlink.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_partner:") || tag.startsWith("conv_id:"));
// 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 erst beide NPCs!");
return true;
}
UUID s1 = UUID.fromString(p.getMetadata("conv_npc1").get(0).asString());
UUID s2 = UUID.fromString(p.getMetadata("conv_npc2").get(0).asString());
NexusLobby.getInstance().getConversationManager().playConversation(s1, s2, args[2]);
break;
default:
return sendConvHelp(p);
}
return true;
}
// --- STANDARD TOOLS (LOOKAT / NAME / ADD) ---
ArmorStand target = getTargetArmorStand(p);
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);
if (nameInput.equalsIgnoreCase("none")) { if (nameInput.equalsIgnoreCase("none")) {
target.setCustomName(""); target.setCustomName("");
target.setCustomNameVisible(false); target.setCustomNameVisible(false);
target.getScoreboardTags().removeIf(tag -> tag.startsWith("asname:"));
p.sendMessage(prefix + "§eName entfernt."); p.sendMessage(prefix + "§eName entfernt.");
} else { } else {
String colored = ChatColor.translateAlternateColorCodes('&', nameInput); String colored = ChatColor.translateAlternateColorCodes('&', nameInput);
target.setCustomName(colored); target.setCustomName(colored);
target.setCustomNameVisible(true); target.setCustomNameVisible(true);
target.getScoreboardTags().removeIf(tag -> tag.startsWith("asname:"));
target.addScoreboardTag("asname:" + nameInput);
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;
}
target.addScoreboardTag("ascmd:" + type + ":" + cmd);
p.sendMessage(prefix + "§aBefehl an ArmorStand gebunden!");
return true; return true;
} }
if (args.length >= 1 && args[0].equalsIgnoreCase("list")) { if (args[0].equalsIgnoreCase("remove")) {
p.sendMessage("§6§lBefehle auf diesem ArmorStand:"); if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; }
for (String tag : target.getScoreboardTags()) { target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
if (tag.startsWith("ascmd:")) { p.sendMessage(prefix + "§eAlle Befehle gelöscht.");
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 +215,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 §7- Sprecher 1");
p.sendMessage("§e/nexuscmd conv select2 §7- Sprecher 2");
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 & Status-Backup");
p.sendMessage("§e/nexuscmd add 0 0 <type> <cmd> §7- Befehl binden"); p.sendMessage("§e/nexuscmd lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/nexuscmd list §7- Befehle anzeigen"); p.sendMessage("§e/nexuscmd add <s1> <s2> bungee <Server> §7- Bungee-Bindung");
p.sendMessage("§e/nexuscmd remove §7- Befehle löschen"); 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

@@ -1,95 +1,191 @@
package de.nexuslobby.modules.armorstandtools; package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Particle;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.EulerAngle;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Set; import java.util.UUID;
/**
* ArmorStandCommand - Vollständige Steuerung für ArmorStands.
* Inklusive Dynamic-Modus, Look-At Funktion, Befehls-Slots und Conversation-Sprecher.
*/
public class ArmorStandCommand implements CommandExecutor { public class ArmorStandCommand implements CommandExecutor {
// Statische Variablen für die aktuell markierten Sprecher
private static UUID speaker1;
private static UUID speaker2;
// Getter-Methoden für die NexusLobby Hauptklasse
public static UUID getSpeaker1() {
return speaker1;
}
public static UUID getSpeaker2() {
return speaker2;
}
// Setter-Methoden (werden vom ASTListener oder der GUI aufgerufen)
public static void setSpeaker1(UUID id) {
speaker1 = id;
}
public static void setSpeaker2(UUID id) {
speaker2 = id;
}
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player p)) return true; if (!(sender instanceof Player p)) {
sender.sendMessage("Nur Spieler können diesen Befehl ausführen.");
if (!p.hasPermission("nexuslobby.armorstand.use")) {
p.sendMessage(Config.prefix() + ChatColor.RED + Config.generalNoPerm);
return true; return true;
} }
// Suche ArmorStand im Umkreis von 5 Blöcken // Fester Prefix für maximale Build-Sicherheit
ArmorStand target = getNearbyArmorStand(p); String prefix = "§8[§6Nexus§8] ";
// Fall 1: Nur /astools -> GUI öffnen if (!p.hasPermission("nexuslobby.armorstand.use")) {
p.sendMessage(prefix + ChatColor.RED + "Dazu hast du keine Rechte!");
return true;
}
// 1. Suche nach dem ArmorStand (Präzise & Umkreis)
ArmorStand target = getBestTargetArmorStand(p);
// Fall: Nur /astools oder /nt (Ohne Argumente)
if (args.length == 0) { if (args.length == 0) {
if (target != null) { if (target != null) {
AST.selectedArmorStand.put(p.getUniqueId(), target); AST.selectedArmorStand.put(p.getUniqueId(), target);
new ArmorStandGUI(target, p); new ArmorStandGUI(target, p);
p.sendMessage(prefix + "§aEditor für ArmorStand geöffnet.");
} else { } else {
p.sendMessage(Config.prefix() + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!"); p.sendMessage(prefix + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!");
} }
return true; return true;
} }
// Fall 2: Unterbefehle (add, remove, list)
String sub = args[0].toLowerCase(); String sub = args[0].toLowerCase();
// Globaler Reload
if (sub.equals("reload")) {
NexusLobby.getInstance().reloadPlugin();
p.sendMessage(prefix + "§aKonfiguration und Module wurden neu geladen.");
return true;
}
// Für alle anderen Aktionen ist ein ArmorStand zwingend erforderlich
if (target == null) { if (target == null) {
p.sendMessage(Config.prefix() + ChatColor.RED + "Du musst nah an einem ArmorStand stehen!"); p.sendMessage(prefix + ChatColor.RED + "Du musst einen ArmorStand anschauen oder direkt davor stehen!");
return true; return true;
} }
switch (sub) { switch (sub) {
case "addplayer": // /astools addplayer <command> case "dynamic":
if (args.length < 2) return sendHelp(p); if (!p.hasPermission("nexuslobby.armorstand.dynamic")) {
String pCmd = buildString(args, 1); p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Dynamic-NPCs!");
target.addScoreboardTag("ascmd:player:" + pCmd); return true;
p.sendMessage(Config.prefix() + "§aBefehl (Player) hinzugefügt: §e" + pCmd); }
if (NexusLobby.getInstance().getDynamicArmorStandModule() != null) {
if (target.getScoreboardTags().contains("as_dynamic")) {
target.removeScoreboardTag("as_dynamic");
p.sendMessage(prefix + "§c§l[-] §7Dynamic-Modus §cdeaktiviert§7.");
p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 15, 0.3, 0.3, 0.3, 0.05);
} else {
target.addScoreboardTag("as_dynamic");
p.sendMessage(prefix + "§a§l[+] §7Dynamic-Modus §aaktiviert§7.");
p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 25, 0.5, 0.5, 0.5, 0.1);
}
NexusLobby.getInstance().getDynamicArmorStandModule().toggleDynamicStatus(target);
} else {
p.sendMessage(prefix + "§cFehler: Dynamic-Modul ist nicht aktiv!");
}
break; break;
case "addconsole": // /astools addconsole <command> case "lookat":
if (args.length < 2) return sendHelp(p); if (!p.hasPermission("nexuslobby.armorstand.lookat")) {
String cCmd = buildString(args, 1); p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Look-At!");
target.addScoreboardTag("ascmd:console:" + cCmd); return true;
p.sendMessage(Config.prefix() + "§aBefehl (Konsole) hinzugefügt: §e" + cCmd); }
if (target.getScoreboardTags().contains("as_lookat")) {
target.removeScoreboardTag("as_lookat");
// Kopf-Pose zurücksetzen für sauberen Übergang
target.setHeadPose(new EulerAngle(0, 0, 0));
p.sendMessage(prefix + "§c§l[-] §7Blickkontakt §cdeaktiviert§7.");
p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 10, 0.2, 0.2, 0.2, 0.02);
} else {
target.addScoreboardTag("as_lookat");
p.sendMessage(prefix + "§a§l[+] §7Blickkontakt §aaktiviert§7.");
p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 15, 0.4, 0.4, 0.4, 0.05);
}
break; break;
case "addbungee": // /astools addbungee <server> case "addplayer":
if (args.length < 2) return sendHelp(p); if (args.length < 4) return sendHelp(p, prefix);
String server = args[1]; String s1P = args[1];
target.addScoreboardTag("ascmd:bungee:" + server); String s2P = args[2];
p.sendMessage(Config.prefix() + "§aBefehl (Bungee) hinzugefügt: §eConnect zu " + server); String pCmd = buildString(args, 3);
target.addScoreboardTag("ascmd:" + s1P + ":" + s2P + ":player:" + pCmd);
p.sendMessage(prefix + "§aBefehl (Player) auf Slot " + s1P + "/" + s2P + " gespeichert.");
break; break;
case "remove": // /astools remove (Löscht alle ascmd Tags) case "addconsole":
Set<String> tags = target.getScoreboardTags(); if (args.length < 4) return sendHelp(p, prefix);
tags.removeIf(tag -> tag.startsWith("ascmd:")); String s1C = args[1];
p.sendMessage(Config.prefix() + "§cAlle Befehle von diesem ArmorStand entfernt."); String s2C = args[2];
String cCmd = buildString(args, 3);
target.addScoreboardTag("ascmd:" + s1C + ":" + s2C + ":console:" + cCmd);
p.sendMessage(prefix + "§aBefehl (Konsole) auf Slot " + s1C + "/" + s2C + " gespeichert.");
break; break;
case "reload": case "remove":
Config.load(); target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:"));
p.sendMessage(Config.prefix() + "§aKonfiguration neu geladen."); p.sendMessage(prefix + "§cAlle Befehls-Tags wurden entfernt.");
break; break;
default: default:
sendHelp(p); sendHelp(p, prefix);
break; break;
} }
return true; return true;
} }
private ArmorStand getNearbyArmorStand(Player p) { private ArmorStand getBestTargetArmorStand(Player p) {
for (Entity e : p.getNearbyEntities(5, 5, 5)) { ArmorStand best = null;
if (e instanceof ArmorStand as) return as; double bestDot = 0.95;
for (Entity e : p.getNearbyEntities(8, 8, 8)) {
if (e instanceof ArmorStand as) {
double dot = p.getLocation().getDirection().dot(
as.getLocation().toVector().subtract(p.getLocation().toVector()).normalize()
);
if (dot > bestDot) {
bestDot = dot;
best = as;
}
}
} }
return null;
if (best == null) {
for (Entity e : p.getNearbyEntities(4, 4, 4)) {
if (e instanceof ArmorStand as) {
return as;
}
}
}
return best;
} }
private String buildString(String[] args, int start) { private String buildString(String[] args, int start) {
@@ -100,14 +196,16 @@ public class ArmorStandCommand implements CommandExecutor {
return sb.toString(); return sb.toString();
} }
private boolean sendHelp(Player p) { private boolean sendHelp(Player p, String prefix) {
p.sendMessage("§6§lArmorStandTools Hilfe:"); p.sendMessage(" ");
p.sendMessage("§e/astools §7- Öffnet Editor"); p.sendMessage("§8§m-----------§r " + prefix + "§8§m-----------");
p.sendMessage("§e/astools addplayer <cmd> §7- Befehl als Spieler ausführen"); p.sendMessage("§e/astools §7- Editor GUI öffnen");
p.sendMessage("§e/astools addconsole <cmd> §7- Befehl als Konsole ausführen"); p.sendMessage("§b/astools dynamic §7- Näherung/Zeit Logik");
p.sendMessage("§e/astools addbungee <server> §7- Serverwechsel"); p.sendMessage("§b/astools lookat §7- Blickkontakt umschalten");
p.sendMessage("§e/astools remove §7- Entfernt alle Befehle"); p.sendMessage("§e/astools addplayer <S1> <S2> <cmd> §7- Befehl (Spieler)");
p.sendMessage("§e/astools reload §7- Lädt Config neu"); p.sendMessage("§e/astools addconsole <S1> <S2> <cmd> §7- Befehl (Konsole)");
p.sendMessage("§e/astools remove §7- Befehle löschen");
p.sendMessage("§8§m---------------------------------------");
return true; return true;
} }
} }

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,80 @@
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;
public class ArmorStandStatusModule implements Module {
@Override
public String getName() {
return "ArmorStandStatus";
}
@Override
public void onEnable() {
// Alle 10 Sekunden prüfen
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::updateAllServerArmorStands, 100L, 200L);
}
@Override
public void onDisable() {}
private void updateAllServerArmorStands() {
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntitiesByClass(ArmorStand.class)) {
if (entity instanceof ArmorStand as) {
checkAndRefreshStatus(as);
}
}
}
}
private void checkAndRefreshStatus(ArmorStand as) {
String bungeeTag = null;
for (String tag : as.getScoreboardTags()) {
if (tag.startsWith("ascmd:bungee:")) {
bungeeTag = tag.replace("ascmd:bungee:", "");
break;
}
}
if (bungeeTag == null) return;
String serverName = bungeeTag.toLowerCase();
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(), () -> {
String originalDisplayName = getOriginalName(as);
if (originalDisplayName == null) return;
String translatedName = ChatColor.translateAlternateColorCodes('&', originalDisplayName);
if (isOnline) {
// Zeigt nur den normalen Namen an, wenn online
as.setCustomName(translatedName);
} else {
// Zeigt den Namen an und darunter (getrennt durch Leerzeichen/Format) den Status
// Da Minecraft Namen meist einzeilig sind, nutzen wir eine klare farbliche Trennung
as.setCustomName(translatedName + " §7- §cOffline");
}
});
});
}
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,166 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class ConversationManager {
private final NexusLobby plugin;
private File file;
private FileConfiguration config;
public ConversationManager(NexusLobby plugin) {
this.plugin = plugin;
setupFile();
}
public void setupFile() {
this.file = new File(plugin.getDataFolder(), "conversations.yml");
if (!file.exists()) {
try {
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdirs();
}
plugin.saveResource("conversations.yml", false);
} catch (Exception e) {
try {
file.createNewFile();
YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(file);
defaultConfig.set("conversations.test.dialogue", Arrays.asList(
"&eNPC 1: &7Hallo!",
"&aNPC 2: &7Hi, wie geht es dir?",
"&eNPC 1: &7Bestens, danke!"
));
defaultConfig.save(file);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
this.config = YamlConfiguration.loadConfiguration(file);
}
public void saveLink(UUID id1, UUID id2, String dialogId) {
config.set("links." + id1.toString() + ".partner", id2.toString());
config.set("links." + id1.toString() + ".dialog", dialogId); // WICHTIG: ".dialog"
saveConfig();
}
public void removeLink(UUID id) {
if (config.contains("links." + id.toString())) {
config.set("links." + id.toString(), null);
saveConfig();
}
}
public void startAllAutomatedConversations() {
if (config.getConfigurationSection("links") == null) return;
for (String npc1String : config.getConfigurationSection("links").getKeys(false)) {
try {
UUID id1 = UUID.fromString(npc1String);
UUID id2 = UUID.fromString(config.getString("links." + npc1String + ".partner"));
String dialogId = config.getString("links." + npc1String + ".dialog"); // WICHTIG: ".dialog"
Entity e1 = Bukkit.getEntity(id1);
Entity e2 = Bukkit.getEntity(id2);
if (e1 instanceof ArmorStand as1 && e2 instanceof ArmorStand as2) {
if (as1.getNearbyEntities(15, 15, 15).stream().anyMatch(e -> e instanceof Player)) {
playConversation(id1, id2, dialogId);
}
}
} catch (Exception ignored) {}
}
}
public List<String> getConversationIds() {
if (config == null || !config.contains("conversations")) {
return new ArrayList<>();
}
return new ArrayList<>(config.getConfigurationSection("conversations").getKeys(false));
}
public void playConversation(UUID id1, UUID id2, String key) {
// Sicherstellen, dass wir auf "conversations.KEY.dialogue" prüfen
if (config == null || !config.contains("conversations." + key)) {
Bukkit.getLogger().warning("[NexusLobby] Dialog-ID '" + key + "' nicht in conversations.yml gefunden!");
return;
}
List<String> lines = config.getStringList("conversations." + key + ".dialogue");
if (lines == null || lines.isEmpty()) return;
new BukkitRunnable() {
int step = 0;
@Override
public void run() {
if (step >= lines.size()) {
this.cancel();
return;
}
UUID speakerUUID = (step % 2 == 0) ? id1 : id2;
Entity entity = Bukkit.getEntity(speakerUUID);
if (entity instanceof ArmorStand as) {
as.getWorld().playSound(as.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 1.5f);
showBubble(as, lines.get(step));
} else {
this.cancel();
return;
}
step++;
}
}.runTaskTimer(plugin, 0L, 70L);
}
private void showBubble(ArmorStand as, String text) {
Location loc = as.getEyeLocation().add(0, 0.6, 0);
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);
});
Bukkit.getScheduler().runTaskLater(plugin, bubble::remove, 60L);
}
private String ChatColorTranslate(String text) {
return text.replace("&", "§");
}
private void saveConfig() {
try {
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
}
public void reload() {
this.config = YamlConfiguration.loadConfiguration(file);
}
}

View File

@@ -0,0 +1,132 @@
package de.nexuslobby.modules.armorstandtools;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.EulerAngle;
public class DynamicArmorStandModule implements Module {
private final NamespacedKey npcKey = new NamespacedKey(NexusLobby.getInstance(), "dynamic_npc");
@Override
public String getName() {
return "DynamicArmorStands";
}
@Override
public void onEnable() {
startUpdateTask();
Bukkit.getLogger().info("§a[NexusLobby] DynamicArmorStandModule aktiv: Ingame-Zeit & Sternen-Effekt geladen.");
}
private void startUpdateTask() {
// Alle 5 Ticks (0.25s) für flüssige Partikel
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::updateAllArmorStands, 20L, 5L);
}
public void updateAllArmorStands() {
for (World world : Bukkit.getWorlds()) {
long time = world.getTime();
boolean isIngameNight = (time >= 13000 && time <= 23000);
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand as) {
if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE) ||
as.getScoreboardTags().contains("as_dynamic")) {
applyDynamicChanges(as, isIngameNight);
if (isIngameNight && as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
spawnStarParticles(as);
}
}
}
}
}
}
private void applyDynamicChanges(ArmorStand as, boolean isNight) {
if (!as.hasArms()) as.setArms(true);
// --- SPIELER-NÄHE ---
boolean playerNearby = false;
for (Entity nearby : as.getNearbyEntities(2.0, 2.0, 2.0)) {
if (nearby instanceof Player) { playerNearby = true; break; }
}
if (playerNearby) {
if (as.getEquipment().getItemInMainHand().getType() != Material.STONE_SWORD) {
as.getEquipment().setItemInMainHand(new ItemStack(Material.STONE_SWORD));
as.setRightArmPose(new EulerAngle(Math.toRadians(-80), Math.toRadians(-10), 0));
}
} else {
if (as.getEquipment().getItemInMainHand().getType() == Material.STONE_SWORD) {
as.getEquipment().setItemInMainHand(null);
as.setRightArmPose(EulerAngle.ZERO);
}
}
// --- INGAME-ZEIT ---
if (isNight) {
if (as.getEquipment().getItemInOffHand().getType() != Material.TORCH) {
as.getEquipment().setItemInOffHand(new ItemStack(Material.TORCH));
as.setLeftArmPose(new EulerAngle(Math.toRadians(-50), Math.toRadians(15), 0));
}
} else {
if (as.getEquipment().getItemInOffHand().getType() == Material.TORCH) {
as.getEquipment().setItemInOffHand(null);
as.setLeftArmPose(EulerAngle.ZERO);
}
}
}
private void spawnStarParticles(ArmorStand as) {
Location loc = as.getLocation();
double yawRad = Math.toRadians(loc.getYaw());
// Vektoren für Blickrichtung und Seite
double dirX = -Math.sin(yawRad);
double dirZ = Math.cos(yawRad);
double sideX = Math.cos(yawRad);
double sideZ = Math.sin(yawRad);
// Position: Vorne (0.4), Links (0.5), Oben (1.45)
double finalX = loc.getX() + (dirX * 0.8) + (sideX * 0.2);
double finalY = loc.getY() + 1.45;
double finalZ = loc.getZ() + (dirZ * 0.8) + (sideZ * 0.2);
Location particleLoc = new Location(as.getWorld(), finalX, finalY, finalZ);
as.getWorld().spawnParticle(Particle.WAX_OFF, particleLoc, 2, 0.05, 0.05, 0.05, 0.01);
}
public void toggleDynamicStatus(ArmorStand as) {
if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE)) {
as.getPersistentDataContainer().remove(npcKey);
as.removeScoreboardTag("as_dynamic");
as.getEquipment().setItemInMainHand(null);
as.getEquipment().setItemInOffHand(null);
as.setRightArmPose(EulerAngle.ZERO);
as.setLeftArmPose(EulerAngle.ZERO);
} else {
as.getPersistentDataContainer().set(npcKey, PersistentDataType.BYTE, (byte) 1);
as.addScoreboardTag("as_dynamic");
applyDynamicChanges(as, (as.getWorld().getTime() >= 13000 && as.getWorld().getTime() <= 23000));
}
}
@Override
public void onDisable() {
// Bereinigung falls nötig
}
}

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

@@ -4,28 +4,37 @@ import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module; import de.nexuslobby.api.Module;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Particle;
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.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class GadgetModule implements Module, Listener { public class GadgetModule implements Module, Listener {
private final Map<UUID, Balloon> activeBalloons = new HashMap<>(); private final Map<UUID, Balloon> activeBalloons = new HashMap<>();
private final Map<UUID, ParticleEffect> activeEffects = new HashMap<>(); private final Map<UUID, ParticleEffect> activeEffects = new HashMap<>();
private final Set<UUID> activeShields = new HashSet<>();
// Titel für die verschiedenen Inventare zur Identifikation
private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü"; private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü";
private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons"; private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons";
private final String PARTICLE_TITLE = "§b§lGadgets §8- §dPartikel"; private final String PARTICLE_TITLE = "§b§lGadgets §8- §dPartikel";
private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges"; private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges";
private final String HAT_TITLE = "§b§lGadgets §8- §aHüte & Köpfe";
private final String PET_TITLE = "§b§lGadgets §8- §dBegleiter";
@Override @Override
public String getName() { return "Gadgets"; } public String getName() { return "Gadgets"; }
@@ -33,39 +42,104 @@ public class GadgetModule implements Module, Listener {
@Override @Override
public void onEnable() { public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
PetManager.updatePets();
activeBalloons.values().forEach(Balloon::update); activeBalloons.values().forEach(Balloon::update);
activeEffects.forEach((uuid, effect) -> {
Player p = Bukkit.getPlayer(uuid); for (Player p : Bukkit.getOnlinePlayers()) {
if (p != null && p.isOnline()) effect.update(p); UUID uuid = p.getUniqueId();
}); handleSpecialHatEffects(p);
if (activeEffects.containsKey(uuid)) activeEffects.get(uuid).update(p);
if (activeShields.contains(uuid)) ShieldTask.handleShield(p);
}
}, 1L, 1L); }, 1L, 1L);
} }
// --- GUI ÖFFNER --- private void handleSpecialHatEffects(Player p) {
ItemStack hat = p.getInventory().getHelmet();
if (hat == null || hat.getType() == Material.AIR) return;
switch (hat.getType()) {
case CAMPFIRE:
p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02);
break;
case SPAWNER:
p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02);
break;
case SEA_LANTERN:
case BEACON:
p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 2.1, 0), 1, 0.1, 0.1, 0.1, 0.03);
break;
case ENCHANTING_TABLE:
p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5);
break;
}
}
public void openGUI(Player player) { public void openGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 27, MAIN_TITLE); Inventory gui = Bukkit.createInventory(null, 27, MAIN_TITLE);
fillEdges(gui); fillEdges(gui);
gui.setItem(10, createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter")); gui.setItem(10, createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter"));
gui.setItem(13, createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte für zwischendurch")); gui.setItem(11, createItem(Material.GOLDEN_HELMET, "§a§lHüte", "§7Setze dir etwas auf den Kopf"));
gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Wähle magische Effekte")); gui.setItem(13, createItem(Material.BONE, "§d§lBegleiter", "§7Echte Tiere, die dir folgen"));
gui.setItem(15, createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte"));
gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Magische Auren & Effekte"));
gui.setItem(22, createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen")); gui.setItem(22, createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen"));
player.openInventory(gui);
}
private void openHatGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 45, HAT_TITLE);
fillEdges(gui);
gui.setItem(10, createItem(Material.JACK_O_LANTERN, "§6Kürbis-Hut", "§7Es ist immer Halloween!"));
gui.setItem(11, createItem(Material.SEA_LANTERN, "§bMeeres-Leuchten", "§7§oEffekt: Glitzern"));
gui.setItem(12, createItem(Material.GLOWSTONE, "§eGlowstone-Kopf", "§7Werde zur Lampe"));
gui.setItem(13, createItem(Material.TNT, "§cExplosiv-Hut", "§7Vorsicht, heiß!"));
gui.setItem(14, createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?"));
gui.setItem(15, createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte"));
gui.setItem(16, createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!"));
gui.setItem(19, createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig..."));
gui.setItem(20, createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig"));
gui.setItem(21, createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land"));
gui.setItem(22, createItem(Material.SPAWNER, "§8Monster-Käfig", "§7§oEffekt: Flammen"));
gui.setItem(23, createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln"));
gui.setItem(24, createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf"));
gui.setItem(25, createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig"));
gui.setItem(28, createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast"));
gui.setItem(29, createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!"));
gui.setItem(30, createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern"));
gui.setItem(31, createItem(Material.CONDUIT, "§3Auge des Meeres", "§7Die Macht von Atlantis"));
gui.setItem(32, createItem(Material.ENCHANTING_TABLE, "§dMagier", "§7§oEffekt: Runen"));
gui.setItem(33, createItem(Material.CAMPFIRE, "§cHeißer Kopf", "§7§oEffekt: Rauch"));
gui.setItem(34, createItem(Material.SKELETON_SKULL, "§7Skelett", "§7Ein wenig gruselig"));
gui.setItem(40, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
player.openInventory(gui);
}
private void openPetGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 27, PET_TITLE);
fillEdges(gui);
gui.setItem(11, createItem(Material.BONE, "§fWolf", "§7Ein treuer Begleiter"));
gui.setItem(13, createItem(Material.CAT_SPAWN_EGG, "§6Katze", "§7Ein verschmuster Freund"));
gui.setItem(15, createItem(Material.PANDA_SPAWN_EGG, "§aPanda", "§7Ein gemütlicher Zeitgenosse"));
gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
player.openInventory(gui); player.openInventory(gui);
} }
private void openBalloonGUI(Player player) { private void openBalloonGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 36, BALLOON_TITLE); Inventory gui = Bukkit.createInventory(null, 36, BALLOON_TITLE);
fillEdges(gui); fillEdges(gui);
Material[] wools = {Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL, Material[] wools = {Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL,
Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL, Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL,
Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, Material.BROWN_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, Material.BROWN_WOOL,
Material.GREEN_WOOL, Material.RED_WOOL}; Material.GREEN_WOOL, Material.RED_WOOL};
int slot = 10; int slot = 10;
for (Material m : wools) { for (Material m : wools) {
if (slot == 17) slot = 19; if (slot == 17) slot = 19;
@@ -78,11 +152,9 @@ public class GadgetModule implements Module, Listener {
private void openParticleGUI(Player player) { private void openParticleGUI(Player player) {
Inventory gui = Bukkit.createInventory(null, 27, PARTICLE_TITLE); Inventory gui = Bukkit.createInventory(null, 27, PARTICLE_TITLE);
fillEdges(gui); fillEdges(gui);
gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe in der Lobby"));
gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe")); gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Lass es brennen!"));
gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Werde feurig")); gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Deine persönliche Abkühlung"));
gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Lass es regnen"));
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);
} }
@@ -90,70 +162,91 @@ 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(13, createItem(Material.EGG, "§f§lChicken-Rain", "§7Lass es Küken regnen!")); gui.setItem(13, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz"));
gui.setItem(15, createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!"));
gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü"));
player.openInventory(gui); player.openInventory(gui);
} }
// --- EVENT HANDLER ---
@EventHandler @EventHandler
public void onInventoryClick(InventoryClickEvent event) { public void onInventoryClick(InventoryClickEvent event) {
String title = event.getView().getTitle(); String title = event.getView().getTitle();
if (!title.equals(MAIN_TITLE) && !title.equals(BALLOON_TITLE) && !title.equals(PARTICLE_TITLE) && !title.equals(FUN_TITLE)) return; if (!title.startsWith("§b§lGadgets")) return;
event.setCancelled(true); event.setCancelled(true);
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
ItemStack item = event.getCurrentItem(); ItemStack item = event.getCurrentItem();
if (item == null || item.getType() == Material.AIR) return; if (item == null || item.getType() == Material.AIR) return;
// Zurück-Button Logik if (item.getType() == Material.ARROW) { openGUI(player); return; }
if (item.getType() == Material.ARROW) {
openGUI(player);
return;
}
// HAUPTMENÜ LOGIK
if (title.equals(MAIN_TITLE)) { if (title.equals(MAIN_TITLE)) {
if (item.getType() == Material.LEAD) openBalloonGUI(player); if (item.getType() == Material.LEAD) openBalloonGUI(player);
else if (item.getType() == Material.GOLDEN_HELMET) openHatGUI(player);
else if (item.getType() == Material.BONE) openPetGUI(player);
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) { else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); }
removeGadgets(player); }
else if (title.equals(HAT_TITLE)) {
if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) {
HatManager.setHat(player, item.getType(), item.getItemMeta().getDisplayName());
player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1);
player.closeInventory(); player.closeInventory();
} }
} }
else if (title.equals(PET_TITLE)) {
// BALLON MENÜ LOGIK if (item.getType() == Material.BONE) PetManager.spawnEntityPet(player, "WOLF");
else if (item.getType() == Material.CAT_SPAWN_EGG) PetManager.spawnEntityPet(player, "CAT");
else if (item.getType() == Material.PANDA_SPAWN_EGG) PetManager.spawnEntityPet(player, "PANDA");
player.sendMessage("§8[§6Nexus§8] §dDein Pet wurde gerufen!");
player.closeInventory();
}
else if (title.equals(BALLOON_TITLE)) { else if (title.equals(BALLOON_TITLE)) {
if (item.getType().toString().endsWith("_WOOL")) { if (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 ausgerüstet!"); player.sendMessage("§8[§6Nexus§8] §aBallon aktiviert!");
player.closeInventory(); player.closeInventory();
} }
} }
// PARTIKEL MENÜ LOGIK
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!");
if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { player.closeInventory();
player.sendMessage("§8[§6Nexus§8] §aEffekt aktiviert!");
player.closeInventory();
}
} }
// FUN MENÜ LOGIK
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] §fEs regnet jetzt Hühner!"); player.sendMessage("§8[§6Nexus§8] §fHühnerregen gestartet!");
player.closeInventory(); player.closeInventory();
} else if (item.getType() == Material.FISHING_ROD) {
player.getInventory().addItem(createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren"));
player.closeInventory();
} else if (item.getType() == Material.SHIELD) {
if (activeShields.contains(player.getUniqueId())) {
activeShields.remove(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §cSchutzzone deaktiviert.");
} else {
activeShields.add(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §5Schutzzone aktiviert!");
}
player.closeInventory();
}
}
}
@EventHandler
public void onFish(PlayerFishEvent event) {
Player player = event.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand();
if (item != null && item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) {
if (event.getState() == PlayerFishEvent.State.IN_GROUND || event.getState() == PlayerFishEvent.State.REEL_IN || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) {
if (event.getHook() != null) {
GrapplingHook.pullPlayer(player, event.getHook().getLocation());
}
} }
} }
} }
@@ -164,15 +257,17 @@ public class GadgetModule implements Module, Listener {
activeBalloons.remove(player.getUniqueId()); activeBalloons.remove(player.getUniqueId());
} }
activeEffects.remove(player.getUniqueId()); activeEffects.remove(player.getUniqueId());
player.sendMessage("§8[§6Nexus§8] §cAlle Gadgets wurden entfernt."); activeShields.remove(player.getUniqueId());
PetManager.removePet(player);
HatManager.removeHat(player);
player.getInventory().remove(Material.FISHING_ROD);
player.sendMessage("§8[§6Nexus§8] §cAlle Gadgets abgelegt.");
} }
private void fillEdges(Inventory inv) { private void fillEdges(Inventory inv) {
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) { if (i < 9 || i >= inv.getSize() - 9 || i % 9 == 0 || (i + 1) % 9 == 0) inv.setItem(i, glass);
inv.setItem(i, glass);
}
} }
} }
@@ -182,7 +277,7 @@ public class GadgetModule implements Module, Listener {
if (meta != null) { if (meta != null) {
meta.setDisplayName(name); meta.setDisplayName(name);
if (lore != null) { if (lore != null) {
java.util.List<String> l = new java.util.ArrayList<>(); List<String> l = new ArrayList<>();
l.add(lore); l.add(lore);
meta.setLore(l); meta.setLore(l);
} }
@@ -193,8 +288,10 @@ public class GadgetModule implements Module, Listener {
@Override @Override
public void onDisable() { public void onDisable() {
PetManager.clearAll();
activeBalloons.values().forEach(Balloon::remove); activeBalloons.values().forEach(Balloon::remove);
activeBalloons.clear(); activeBalloons.clear();
activeEffects.clear(); activeEffects.clear();
activeShields.clear();
} }
} }

View File

@@ -0,0 +1,37 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class GrapplingHook {
public static void pullPlayer(Player player, Location target) {
Location playerLoc = player.getLocation();
// Vektor vom Spieler zum Ziel berechnen
double distance = target.distance(playerLoc);
// Wenn das Ziel zu nah oder zu weit weg ist, nichts tun
if (distance < 2 || distance > 50) return;
// Berechnung des Wurfs (Vektor)
Vector v = target.toVector().subtract(playerLoc.toVector());
// Den Vektor normalisieren und skalieren (Stärke des Zugs)
v.multiply(0.3); // Basis-Geschwindigkeit
v.setY(v.getY() * 0.6 + 0.5); // Etwas mehr Höhe für den Bogen-Effekt
// Geschwindigkeit begrenzen, damit man nicht aus der Map schießt
if (v.length() > 2.5) {
v.normalize().multiply(2.5);
}
player.setVelocity(v);
// Sound-Effekt für das "Ziehen"
player.playSound(playerLoc, Sound.ENTITY_WIND_CHARGE_WIND_BURST, 1.0f, 1.2f);
player.playSound(playerLoc, Sound.ITEM_TRIDENT_RIPTIDE_1, 0.5f, 1.5f);
}
}

View File

@@ -0,0 +1,26 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class HatManager {
public static void setHat(Player player, Material material, String name) {
ItemStack hat = new ItemStack(material);
ItemMeta meta = hat.getItemMeta();
if (meta != null) {
meta.setDisplayName("§6Hut: " + name);
hat.setItemMeta(meta);
}
// Den Gegenstand auf den Kopf setzen
player.getInventory().setHelmet(hat);
player.sendMessage("§8[§6Nexus§8] §aDu trägst nun: " + name);
}
public static void removeHat(Player player) {
player.getInventory().setHelmet(null);
}
}

View File

@@ -0,0 +1,138 @@
package de.nexuslobby.modules.gadgets;
import de.nexuslobby.NexusLobby;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PetManager implements Listener {
private static final Map<UUID, Entity> activePets = new HashMap<>();
public PetManager() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
}
/**
* Spawnt ein echtes Tier-Entity für den Spieler.
*/
public static void spawnEntityPet(Player player, String type) {
removePet(player);
EntityType entityType;
try {
entityType = EntityType.valueOf(type);
} catch (IllegalArgumentException e) {
player.sendMessage("§cFehler: Tier-Typ nicht gefunden.");
return;
}
Location loc = player.getLocation();
Entity pet = player.getWorld().spawnEntity(loc, entityType);
pet.setCustomName("§d" + player.getName() + "'s " + capitalize(type.toLowerCase()));
pet.setCustomNameVisible(true);
pet.setInvulnerable(true);
pet.setPersistent(false);
if (pet instanceof LivingEntity) {
LivingEntity le = (LivingEntity) pet;
le.setRemoveWhenFarAway(false);
// Verhindert, dass das Pet andere angreift
if (le instanceof Tameable) {
((Tameable) le).setTamed(true);
((Tameable) le).setOwner(player);
}
}
activePets.put(player.getUniqueId(), pet);
}
/**
* Steuert das Folgen der Tiere. Wird vom GadgetModule-Timer aufgerufen.
*/
public static void updatePets() {
for (Map.Entry<UUID, Entity> entry : activePets.entrySet()) {
Player owner = Bukkit.getPlayer(entry.getKey());
Entity pet = entry.getValue();
if (owner == null || !owner.isOnline() || pet.isDead()) {
continue;
}
// Wenn das Pet in einer anderen Welt ist oder zu weit weg, teleportiere es
if (!pet.getWorld().equals(owner.getWorld()) || pet.getLocation().distance(owner.getLocation()) > 10) {
pet.teleport(owner.getLocation());
continue;
}
// Sanftes Folgen: Wenn das Pet weiter als 3 Blöcke weg ist
if (pet.getLocation().distance(owner.getLocation()) > 3) {
Location target = owner.getLocation().clone().add(owner.getLocation().getDirection().multiply(-1.5));
target.setY(owner.getLocation().getY());
// Teleportiert das Pet leicht zum Ziel (simuliert Laufen)
pet.teleport(pet.getLocation().add(target.toVector().subtract(pet.getLocation().toVector()).normalize().multiply(0.2)));
// Blickrichtung anpassen
Location lookAt = pet.getLocation();
lookAt.setDirection(owner.getLocation().toVector().subtract(pet.getLocation().toVector()));
pet.teleport(lookAt);
}
}
}
public static void removePet(Player player) {
if (activePets.containsKey(player.getUniqueId())) {
activePets.get(player.getUniqueId()).remove();
activePets.remove(player.getUniqueId());
}
}
public static void clearAll() {
for (Entity pet : activePets.values()) {
pet.remove();
}
activePets.clear();
}
private static String capitalize(String str) {
if (str == null || str.isEmpty()) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
// --- Events um die Pets zu schützen ---
@EventHandler
public void onPetDamage(EntityDamageEvent event) {
if (activePets.containsValue(event.getEntity())) {
event.setCancelled(true);
}
}
@EventHandler
public void onPetTarget(EntityTargetEvent event) {
if (activePets.containsValue(event.getEntity())) {
event.setCancelled(true);
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
removePet(event.getPlayer());
}
}

View File

@@ -0,0 +1,32 @@
package de.nexuslobby.modules.gadgets;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class ShieldTask {
public static void handleShield(Player owner) {
// Erzeuge einen Partikel-Ring um den Spieler
for (double i = 0; i < Math.PI * 2; i += Math.PI / 8) {
double x = Math.cos(i) * 2.2;
double z = Math.sin(i) * 2.2;
owner.getWorld().spawnParticle(Particle.WITCH, owner.getLocation().add(x, 0.5, z), 1, 0, 0, 0, 0);
}
// Stoße andere Spieler weg
for (Entity entity : owner.getNearbyEntities(2.2, 2.0, 2.2)) {
if (entity instanceof Player && entity != owner) {
Player target = (Player) entity;
Vector direction = target.getLocation().toVector().subtract(owner.getLocation().toVector()).normalize();
direction.multiply(0.4).setY(0.2);
target.setVelocity(direction);
target.playSound(target.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 0.5f);
}
}
}
}

View File

@@ -0,0 +1,85 @@
package de.nexuslobby.commands;
import de.nexuslobby.modules.hologram.HologramModule;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class HoloCommand implements CommandExecutor {
private final HologramModule module;
public HoloCommand(HologramModule module) {
this.module = module;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) return true;
if (!player.hasPermission("nexuslobby.hologram")) {
player.sendMessage("§8[§6Nexus§8] §cKeine Rechte.");
return true;
}
if (args.length < 1) {
sendHelp(player);
return true;
}
if (args[0].equalsIgnoreCase("create")) {
// Wir brauchen id, typ und mindestens ein Wort Text
if (args.length < 4) {
player.sendMessage("§cNutze: /holo create <id> <NONE|FAST|SLOW> <text>");
return true;
}
String id = args[1];
// Der Typ (args[2]) wird im neuen System ignoriert, da wir Seiten nutzen
StringBuilder sb = new StringBuilder();
for (int i = 3; i < args.length; i++) {
sb.append(args[i]).append(i == args.length - 1 ? "" : " ");
}
String fullText = sb.toString().trim();
List<String> pages = new ArrayList<>();
// Support für mehrere Seiten via ";"
if (fullText.contains(";")) {
pages.addAll(Arrays.asList(fullText.split(";")));
} else {
pages.add(fullText);
}
// Aufruf der neuen Methode mit List<String>
module.createHologram(id, player.getLocation(), pages);
player.sendMessage("§8[§6Nexus§8] §aHologramm §e" + id + " §aerstellt (" + pages.size() + " Seiten).");
}
else if (args[0].equalsIgnoreCase("delete")) {
if (args.length < 2) {
player.sendMessage("§cBitte gib eine ID an: /holo delete <id>");
return true;
}
module.removeHologram(args[1]);
player.sendMessage("§8[§6Nexus§8] §cHologramm §e" + args[1] + " §agelöscht.");
} else {
sendHelp(player);
}
return true;
}
private void sendHelp(Player player) {
player.sendMessage("§8§m-----------§r §6Hologramme §8§m-----------");
player.sendMessage("§e/holo create <id> <NONE|FAST|SLOW> <text>");
player.sendMessage("§e/holo delete <id>");
player.sendMessage("§7Nutze §b; §7für neue Seiten.");
player.sendMessage("§7Nutze §b\\n §7für Zeilenumbruch.");
}
}

View File

@@ -0,0 +1,178 @@
package de.nexuslobby.modules.hologram;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Interaction;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HologramModule implements Module, Listener {
private final Map<String, NexusHologram> holograms = new ConcurrentHashMap<>();
private File file;
private FileConfiguration config;
@Override
public String getName() { return "Holograms"; }
@Override
public void onEnable() {
loadConfig();
loadHolograms();
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
// Render-Task: Prüft alle 5 Ticks Sichtbarkeit und Placeholder-Updates
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
holograms.values().forEach(h -> h.renderForPlayer(player));
}
}, 20L, 5L);
}
private void loadConfig() {
file = new File(NexusLobby.getInstance().getDataFolder(), "holograms.yml");
config = YamlConfiguration.loadConfiguration(file);
}
private void loadHolograms() {
// Vorherige Instanzen säubern
holograms.values().forEach(NexusHologram::removeAll);
holograms.clear();
for (String id : config.getKeys(false)) {
String worldName = config.getString(id + ".world");
if (worldName == null) continue;
World world = Bukkit.getWorld(worldName);
if (world == null) continue;
Location loc = new Location(world,
config.getDouble(id + ".x"),
config.getDouble(id + ".y"),
config.getDouble(id + ".z"));
List<String> pages;
if (config.isList(id + ".text")) {
pages = config.getStringList(id + ".text");
} else {
pages = new ArrayList<>();
pages.add(config.getString(id + ".text", "No Text"));
}
holograms.put(id, new NexusHologram(id, loc, pages));
}
}
@EventHandler
public void onInteract(PlayerInteractEntityEvent event) {
// Nur auf Interaction-Entities reagieren (Hologramm-Hitboxen)
if (!(event.getRightClicked() instanceof Interaction)) return;
for (NexusHologram holo : holograms.values()) {
if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) {
holo.nextPage(event.getPlayer());
break;
}
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
// Cleanup alter Entity-Reste, die eventuell noch in der Welt schweben
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
Player p = event.getPlayer();
for (Entity entity : p.getWorld().getEntities()) {
if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) {
// Wenn das Hologramm nicht exakt für diesen Spieler benannt ist -> verstecken
if (!entity.getCustomName().endsWith("_" + p.getName())) {
p.hideEntity(NexusLobby.getInstance(), entity);
}
}
}
}, 10L);
}
public void createHologram(String id, Location loc, List<String> pages) {
// Falls ID bereits existiert, altes Hologramm sauber entfernen
if (holograms.containsKey(id)) {
removeHologram(id);
}
config.set(id + ".world", loc.getWorld().getName());
config.set(id + ".x", loc.getX());
config.set(id + ".y", loc.getY());
config.set(id + ".z", loc.getZ());
config.set(id + ".text", pages);
saveHoloConfig();
NexusHologram holo = new NexusHologram(id, loc, pages);
holograms.put(id, holo);
}
public void removeHologram(String id) {
NexusHologram holo = holograms.remove(id);
if (holo != null) {
// Erst für alle Spieler visuell entfernen
for (Player player : Bukkit.getOnlinePlayers()) {
holo.removeForPlayer(player);
}
// Dann Entities serverseitig löschen
holo.removeAll();
}
config.set(id, null);
saveHoloConfig();
}
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
public void onDisable() {
holograms.values().forEach(NexusHologram::removeAll);
holograms.clear();
}
}

View File

@@ -0,0 +1,118 @@
package de.nexuslobby.modules.hologram;
import de.nexuslobby.NexusLobby;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.entity.Display;
import org.bukkit.entity.Interaction;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class NexusHologram {
private final String id;
private final Location location;
private final List<String> pages;
private final Map<UUID, TextDisplay> playerEntities = new ConcurrentHashMap<>();
private final Map<UUID, Interaction> playerInteractions = new ConcurrentHashMap<>();
private final Map<UUID, Integer> currentPage = new ConcurrentHashMap<>();
public NexusHologram(String id, Location location, List<String> pages) {
this.id = id;
this.location = location;
this.pages = pages;
}
public void nextPage(Player player) {
if (pages.size() <= 1) return;
int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % pages.size();
currentPage.put(player.getUniqueId(), next);
renderForPlayer(player);
}
public void renderForPlayer(Player player) {
if (!player.getWorld().equals(location.getWorld()) || player.getLocation().distanceSquared(location) > 2304) {
removeForPlayer(player);
return;
}
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), 0);
if (pageIdx >= pages.size()) pageIdx = 0;
String rawText = pages.get(pageIdx);
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
rawText = PlaceholderAPI.setPlaceholders(player, rawText);
}
final String finalText = rawText.replace("&", "§").replace("\\n", "\n");
TextDisplay display = playerEntities.get(player.getUniqueId());
if (display == null || !display.isValid()) {
// Text-Display erstellen (Hier lassen wir den Namen zur internen Identifikation,
// aber schalten ihn strikt unsichtbar)
display = location.getWorld().spawn(location, TextDisplay.class, entity -> {
entity.setCustomName("nexus_h_" + id + "_" + player.getName());
entity.setCustomNameVisible(false);
entity.setPersistent(false);
entity.setBillboard(Display.Billboard.CENTER);
entity.setBackgroundColor(Color.fromARGB(0, 0, 0, 0));
entity.setText(finalText);
entity.setInvulnerable(true);
});
// Interaction Entity (Hitbox) erstellen
// FIX: WIR SETZEN KEINEN CUSTOM NAME MEHR.
// Das verhindert zu 100%, dass Minecraft etwas anzeigt.
Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> {
entity.setInteractionWidth(2.5f);
entity.setInteractionHeight(2.0f);
entity.setCustomNameVisible(false);
entity.setPersistent(false);
});
// Nur für den Zielspieler sichtbar machen
for (Player other : Bukkit.getOnlinePlayers()) {
if (!other.getUniqueId().equals(player.getUniqueId())) {
other.hideEntity(NexusLobby.getInstance(), display);
other.hideEntity(NexusLobby.getInstance(), interact);
}
}
playerEntities.put(player.getUniqueId(), display);
playerInteractions.put(player.getUniqueId(), interact);
} else {
if (!display.getText().equals(finalText)) {
display.setText(finalText);
}
}
}
public void removeForPlayer(Player player) {
TextDisplay display = playerEntities.remove(player.getUniqueId());
if (display != null) display.remove();
Interaction interact = playerInteractions.remove(player.getUniqueId());
if (interact != null) interact.remove();
}
public void removeAll() {
playerEntities.values().forEach(TextDisplay::remove);
playerInteractions.values().forEach(Interaction::remove);
playerEntities.clear();
playerInteractions.clear();
currentPage.clear();
}
public boolean isInteractionEntity(UUID entityId) {
// Da wir keinen Namen mehr nutzen, verlassen wir uns rein auf die UUID in der Map
return playerInteractions.values().stream().anyMatch(i -> i.getUniqueId().equals(entityId));
}
}

View File

@@ -0,0 +1,219 @@
package de.nexuslobby.modules.intro;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class IntroModule implements Module, Listener, CommandExecutor {
private final Set<UUID> activeIntro = new HashSet<>();
private final List<Location> points = new ArrayList<>();
private File configFile;
private FileConfiguration config;
// --- Einstellungen ---
private final int TICKS_FLUG = 70; // Dauer der Fahrt zwischen zwei Punkten (3.5 Sek)
private final int TICKS_PAUSE = 30; // Standzeit an jedem Punkt (1.5 Sek)
@Override
public String getName() { return "Intro"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("intro") != null) {
NexusLobby.getInstance().getCommand("intro").setExecutor(this);
}
loadPoints();
}
@Override
public void onDisable() {
activeIntro.clear();
}
private void loadPoints() {
points.clear();
configFile = new File(NexusLobby.getInstance().getDataFolder(), "intro.yml");
if (!configFile.exists()) {
try { configFile.createNewFile(); } catch (IOException ignored) {}
}
config = YamlConfiguration.loadConfiguration(configFile);
List<?> list = config.getList("points");
if (list != null) {
for (Object obj : list) {
if (obj instanceof Location loc) points.add(loc);
}
}
}
private void savePoints() {
config.set("points", points);
try { config.save(configFile); } catch (IOException e) { e.printStackTrace(); }
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (!event.getPlayer().hasPlayedBefore() && points.size() >= 2) {
startIntro(event.getPlayer());
}
}
@EventHandler
public void onSneak(PlayerToggleSneakEvent event) {
if (activeIntro.contains(event.getPlayer().getUniqueId()) && event.isSneaking()) {
stopIntro(event.getPlayer(), true);
}
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player p)) return true;
if (!p.hasPermission("nexuslobby.admin")) return true;
if (args.length == 0) {
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lNexus Intro System (Cinematic)");
p.sendMessage("§e/intro add §7- Punkt hinzufügen");
p.sendMessage("§e/intro clear §7- Alle Punkte löschen");
p.sendMessage("§e/intro start §7- Teste die Fahrt");
p.sendMessage("§8§m------------------------------------");
return true;
}
switch (args[0].toLowerCase()) {
case "add" -> {
points.add(p.getLocation());
savePoints();
p.sendMessage("§8[§6Nexus§8] §aPunkt #" + points.size() + " wurde gesetzt!");
}
case "clear" -> {
points.clear();
savePoints();
p.sendMessage("§8[§6Nexus§8] §cAlle Intro-Punkte wurden gelöscht.");
}
case "start" -> {
if (points.size() < 2) {
p.sendMessage("§8[§6Nexus§8] §cDu brauchst mindestens 2 Punkte für eine Fahrt.");
} else {
startIntro(p);
}
}
}
return true;
}
public void startIntro(Player player) {
activeIntro.add(player.getUniqueId());
player.setGameMode(GameMode.SPECTATOR);
new BukkitRunnable() {
int currentSegment = 0;
int tickInSegment = 0;
boolean isPausing = true;
@Override
public void run() {
try {
if (!player.isOnline() || !activeIntro.contains(player.getUniqueId())) {
this.cancel();
return;
}
if (currentSegment >= points.size() - 1) {
stopIntro(player, false);
this.cancel();
return;
}
Location start = points.get(currentSegment);
Location end = points.get(currentSegment + 1);
if (isPausing) {
// Kamera steht am aktuellen Punkt
player.teleport(start);
tickInSegment++;
if (tickInSegment >= TICKS_PAUSE) {
isPausing = false;
tickInSegment = 0;
}
} else {
// Kamera fliegt zum nächsten Punkt
double progress = (double) tickInSegment / (double) TICKS_FLUG;
// "Smooth Step" für flüssigeres Beschleunigen/Bremsen
double smoothT = progress * progress * (3 - 2 * progress);
Location nextLoc = interpolate(start, end, smoothT);
player.teleport(nextLoc);
tickInSegment++;
if (tickInSegment >= TICKS_FLUG) {
isPausing = true;
tickInSegment = 0;
currentSegment++;
}
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
new TextComponent("§6§lINTRO-TOUR §8| §ePunkt " + (currentSegment + 1) + " §8| §7Sneak zum Abbrechen"));
} catch (Exception e) {
this.cancel();
stopIntro(player, true);
}
}
}.runTaskTimer(NexusLobby.getInstance(), 0L, 1L);
}
private Location interpolate(Location start, Location end, double t) {
double x = start.getX() + (end.getX() - start.getX()) * t;
double y = start.getY() + (end.getY() - start.getY()) * t;
double z = start.getZ() + (end.getZ() - start.getZ()) * t;
// Sanfte Drehung
float startYaw = start.getYaw();
float endYaw = end.getYaw();
// Verhindert ruckartige 360-Grad Dreher
float diff = (endYaw - startYaw) % 360;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
float yaw = startYaw + diff * (float)t;
float pitch = (float) (start.getPitch() + (end.getPitch() - start.getPitch()) * t);
return new Location(start.getWorld(), x, y, z, yaw, pitch);
}
private void stopIntro(Player player, boolean canceled) {
activeIntro.remove(player.getUniqueId());
player.setGameMode(GameMode.ADVENTURE);
player.teleport(player.getWorld().getSpawnLocation());
if (canceled) {
player.sendMessage("§8[§6Nexus§8] §cIntro abgebrochen.");
} else {
player.sendMessage("§8[§6Nexus§8] §aWillkommen auf dem Netzwerk!");
}
}
}

View File

@@ -0,0 +1,298 @@
package de.nexuslobby.modules.mapart;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class MapArtModule implements Module, CommandExecutor {
private File storageFile;
private FileConfiguration storageConfig;
// RAM-Schutz: Cache für bereits geladene Bild-Kacheln
private final Map<String, BufferedImage> tileCache = new ConcurrentHashMap<>();
// Hilfs-Set um parallele Downloads der gleichen URL zu verhindern
private final Map<String, Boolean> loadingShield = new ConcurrentHashMap<>();
@Override
public String getName() { return "MapArt"; }
@Override
public void onEnable() {
if (NexusLobby.getInstance().getCommand("mapart") != null) {
NexusLobby.getInstance().getCommand("mapart").setExecutor(this);
}
initStorage();
reloadMaps();
}
@Override
public void onDisable() {
saveStorage();
tileCache.clear();
}
private void initStorage() {
storageFile = new File(NexusLobby.getInstance().getDataFolder(), "mapart.yml");
if (!storageFile.exists()) {
try {
storageFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
storageConfig = YamlConfiguration.loadConfiguration(storageFile);
}
private void saveStorage() {
try {
storageConfig.save(storageFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private void reloadMaps() {
ConfigurationSection section = storageConfig.getConfigurationSection("active_maps");
if (section == null) return;
for (String idStr : section.getKeys(false)) {
try {
int mapId = Integer.parseInt(idStr);
String url = section.getString(idStr + ".url");
int x = section.getInt(idStr + ".x");
int y = section.getInt(idStr + ".y");
int totalW = section.getInt(idStr + ".totalW");
int totalH = section.getInt(idStr + ".totalH");
@SuppressWarnings("deprecation")
MapView view = Bukkit.getMap(mapId);
if (view != null) {
applyRenderer(view, url, x, y, totalW, totalH);
}
} catch (Exception ignored) {}
}
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) return true;
if (!player.hasPermission("nexuslobby.mapart")) {
player.sendMessage("§8[§6Nexus§8] §cKeine Rechte.");
return true;
}
if (args.length == 0) {
sendHelp(player);
return true;
}
if (args[0].equalsIgnoreCase("delete")) {
int radius = 3;
if (args.length == 2) {
try {
radius = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
player.sendMessage("§8[§6Nexus§8] §cUngültiger Radius.");
return true;
}
}
deleteNearbyMaps(player, radius);
return true;
}
if (args.length != 2) {
sendHelp(player);
return true;
}
RayTraceResult rayTrace = player.rayTraceBlocks(5);
if (rayTrace == null || rayTrace.getHitBlock() == null || rayTrace.getHitBlockFace() == null) {
player.sendMessage("§8[§6Nexus§8] §cBitte schaue eine Wand direkt an.");
return true;
}
Block targetBlock = rayTrace.getHitBlock();
BlockFace hitFace = rayTrace.getHitBlockFace();
if (hitFace == BlockFace.UP || hitFace == BlockFace.DOWN) {
player.sendMessage("§8[§6Nexus§8] §cAktuell nur an vertikalen Wänden möglich.");
return true;
}
String url = args[0];
int width, height;
try {
String[] parts = args[1].toLowerCase().split("x");
width = Integer.parseInt(parts[0]);
height = Integer.parseInt(parts[1]);
} catch (Exception e) {
player.sendMessage("§8[§6Nexus§8] §cUngültiges Format (z.B. 6x4).");
return true;
}
player.sendMessage("§8[§6Nexus§8] §7Bild wird verarbeitet...");
createRaster(player, targetBlock, hitFace, url, width, height);
return true;
}
private void sendHelp(Player p) {
p.sendMessage("§8§m------------------------------------");
p.sendMessage("§6§lNexus MapArt");
p.sendMessage("§e/mapart <URL> <BxH> §7- Bild erstellen");
p.sendMessage("§e/mapart delete [Radius] §7- Bilder in der Nähe löschen");
p.sendMessage("§8§m------------------------------------");
}
private void deleteNearbyMaps(Player player, int radius) {
AtomicInteger count = new AtomicInteger(0);
player.getNearbyEntities(radius, radius, radius).forEach(entity -> {
if (entity instanceof ItemFrame frame) {
ItemStack item = frame.getItem();
if (item != null && item.getType() == Material.FILLED_MAP && item.getItemMeta() instanceof MapMeta meta) {
if (meta.hasMapView()) {
int id = meta.getMapView().getId();
if (storageConfig.contains("active_maps." + id)) {
storageConfig.set("active_maps." + id, null);
frame.remove();
count.incrementAndGet();
}
}
}
}
});
saveStorage();
player.sendMessage("§8[§6Nexus§8] §aErfolgreich §e" + count.get() + " §aKartenelemente gelöscht.");
}
private void createRaster(Player player, Block startBlock, BlockFace face, String url, int gridW, int gridH) {
BlockFace rightDirection;
switch (face) {
case NORTH: rightDirection = BlockFace.WEST; break;
case SOUTH: rightDirection = BlockFace.EAST; break;
case EAST: rightDirection = BlockFace.NORTH; break;
case WEST: rightDirection = BlockFace.SOUTH; break;
default: rightDirection = BlockFace.EAST;
}
Block origin = startBlock.getRelative(face);
for (int y = 0; y < gridH; y++) {
for (int x = 0; x < gridW; x++) {
Block currentPos = origin.getRelative(rightDirection, x).getRelative(BlockFace.DOWN, y);
spawnPersistentFrame(player, currentPos, face, url, x, y, gridW, gridH);
}
}
player.sendMessage("§8[§6Nexus§8] §aBild-Raster wurde permanent platziert!");
}
private void spawnPersistentFrame(Player player, Block pos, BlockFace face, String url, int x, int y, int totalW, int totalH) {
MapView view = Bukkit.createMap(player.getWorld());
applyRenderer(view, url, x, y, totalW, totalH);
String path = "active_maps." + view.getId();
storageConfig.set(path + ".url", url);
storageConfig.set(path + ".x", x);
storageConfig.set(path + ".y", y);
storageConfig.set(path + ".totalW", totalW);
storageConfig.set(path + ".totalH", totalH);
saveStorage();
ItemStack mapStack = new ItemStack(Material.FILLED_MAP);
MapMeta meta = (MapMeta) mapStack.getItemMeta();
if (meta != null) {
meta.setMapView(view);
mapStack.setItemMeta(meta);
}
try {
ItemFrame frame = player.getWorld().spawn(pos.getLocation(), ItemFrame.class);
frame.setFacingDirection(face);
frame.setItem(mapStack);
frame.setVisible(false);
frame.setFixed(true);
} catch (Exception ignored) {}
}
private void applyRenderer(MapView view, String url, int tileX, int tileY, int totalW, int totalH) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(new MapRenderer() {
private final String cacheKey = url + "_" + totalW + "x" + totalH + "_" + tileX + "_" + tileY;
private boolean errorLogged = false;
@Override
public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player p) {
// 1. Wenn Kachel im Cache, sofort zeichnen (extrem schnell)
if (tileCache.containsKey(cacheKey)) {
canvas.drawImage(0, 0, tileCache.get(cacheKey));
return;
}
// 2. Wenn bereits ein Thread für diese URL lädt, abbrechen (verhindert Spam)
if (loadingShield.containsKey(url)) return;
// 3. Bild asynchron laden
loadingShield.put(url, true);
Bukkit.getScheduler().runTaskAsynchronously(NexusLobby.getInstance(), () -> {
try {
BufferedImage original = ImageIO.read(new URL(url));
if (original != null) {
int targetW = totalW * 128;
int targetH = totalH * 128;
BufferedImage fullScaled = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = fullScaled.createGraphics();
g.drawImage(original.getScaledInstance(targetW, targetH, Image.SCALE_SMOOTH), 0, 0, null);
g.dispose();
// Alle benötigten Kacheln für dieses Bild in den Cache legen
for (int ty = 0; ty < totalH; ty++) {
for (int tx = 0; tx < totalW; tx++) {
String key = url + "_" + totalW + "x" + totalH + "_" + tx + "_" + ty;
tileCache.put(key, fullScaled.getSubimage(tx * 128, ty * 128, 128, 128));
}
}
}
} catch (Exception e) {
if (!errorLogged) {
NexusLobby.getInstance().getLogger().warning("Fehler beim Laden von MapArt: " + url);
errorLogged = true;
}
} finally {
loadingShield.remove(url);
}
});
}
});
}
}

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!");
@@ -151,4 +172,4 @@ public class PortalCommand implements CommandExecutor {
p.sendMessage("§e/portal delete <Name>"); p.sendMessage("§e/portal delete <Name>");
p.sendMessage("§e/portal list"); p.sendMessage("§e/portal list");
} }
} }

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

@@ -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

View File

@@ -1,6 +1,6 @@
name: NexusLobby name: NexusLobby
main: de.nexuslobby.NexusLobby main: de.nexuslobby.NexusLobby
version: "1.0.2" version: "1.0.5"
api-version: "1.21" api-version: "1.21"
author: M_Viper author: M_Viper
description: Modular Lobby Plugin description: Modular Lobby Plugin
@@ -12,49 +12,64 @@ commands:
usage: /portal <args> usage: /portal <args>
permission: nexuslobby.portal permission: nexuslobby.portal
permission-message: "§cKeine Rechte!" permission-message: "§cKeine Rechte!"
giveportalwand: giveportalwand:
description: Gibt das Portal-Werkzeug description: Gibt das Portal-Werkzeug
usage: /giveportalwand usage: /giveportalwand
permission: nexuslobby.portal.give permission: nexuslobby.portal.give
permission-message: "§cDu hast keine Berechtigung dafür." permission-message: "§cDu hast keine Berechtigung dafür."
maintenance: maintenance:
description: Aktiviert oder deaktiviert den Wartungsmodus description: Aktiviert oder deaktiviert den Wartungsmodus
usage: /maintenance <on|off> usage: /maintenance <on|off>
permission: nexuslobby.maintenance permission: nexuslobby.maintenance
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
serverswitcher: serverswitcher:
description: Öffnet die Server Switcher GUI description: Öffnet die Server Switcher GUI
usage: /serverswitcher usage: /serverswitcher
permission: nexuslobby.serverswitcher permission: nexuslobby.serverswitcher
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
settings: settings:
description: Öffnet das Lobby-Einstellungsmenü (Gamerules) description: Öffnet das Lobby-Einstellungsmenü (Gamerules)
usage: /settings usage: /settings
permission: nexuslobby.admin permission: nexuslobby.admin
permission-message: "§cDu hast keine Rechte für die Admin-Einstellungen!" permission-message: "§cDu hast keine Rechte für die Admin-Einstellungen!"
build: build:
description: Aktiviert oder deaktiviert den Baumodus description: Aktiviert oder deaktiviert den Baumodus
usage: /build usage: /build
permission: nexuslobby.build permission: nexuslobby.build
permission-message: "§cDu hast keine Rechte!" permission-message: "§cDu hast keine Rechte!"
nexuslobby: nexuslobby:
description: Zeigt Informationen über das Plugin an oder lädt es neu description: Zeigt Informationen über das Plugin an oder lädt es neu
usage: /nexuslobby [reload] usage: /nexuslobby [reload|setspawn]
aliases: [nexus] aliases: [nexus, lobby]
# --- ArmorStandTools Sektion ---
nexustools: nexustools:
description: Nexus ArmorStand Editor description: Nexus ArmorStand Editor (LookAt, Dynamic, etc.)
aliases: [nt, ntools, astools] aliases: [nt, ntools, astools]
nexuscmd: nexuscmd:
description: Nexus Command Binder description: Nexus Command Binder (Slots 0-9) und Gesprächs-System
aliases: [ncmd, ascmd] usage: /nexuscmd <args>
permission: nexuslobby.armorstand.cmd
permission-message: "§cDu hast keine Rechte!"
aliases: [ncmd, ascmd, conv]
holo:
description: Verwalte Lobby Hologramme (Text-Displays)
usage: /holo <create|delete> <id> [text]
permission: nexuslobby.hologram
mapart:
description: Erstellt ein Bild-Raster auf unsichtbaren Rahmen
usage: /mapart <URL> <BxH>
permission: nexuslobby.mapart
intro:
description: Verwalte und teste die Cinematic Intro Tour
usage: /intro <add|clear|start>
permission: nexuslobby.admin
border:
description: Verwalte die unsichtbare Lobby-Begrenzung
usage: /border <circle|square|disable>
permission: nexuslobby.admin
spawn:
description: Teleportiert dich zum Lobby-Spawnpunkt
usage: /spawn
aliases: [l, hub]
permissions: permissions:
nexuslobby.portal: nexuslobby.portal:
@@ -70,7 +85,7 @@ permissions:
description: Zugriff auf den Server Switcher description: Zugriff auf den Server Switcher
default: true default: true
nexuslobby.admin: nexuslobby.admin:
description: Voller Zugriff auf Lobby-Gamerules, Einstellungen und Reload description: Voller Zugriff auf Lobby-Gamerules, Einstellungen, Intro, Border und Reload
default: op default: op
nexuslobby.build: nexuslobby.build:
description: Erlaubt das Umgehen des Lobby-Schutzes zum Bauen description: Erlaubt das Umgehen des Lobby-Schutzes zum Bauen
@@ -79,5 +94,17 @@ permissions:
description: Erlaubt die Nutzung der NexusTools GUI description: Erlaubt die Nutzung der NexusTools GUI
default: op default: op
nexuslobby.armorstand.cmd: nexuslobby.armorstand.cmd:
description: Erlaubt das Binden von Commands via NexusCmd description: Erlaubt das Binden von Commands und das Verwalten von Conversations
default: op
nexuslobby.armorstand.dynamic:
description: Erlaubt das Markieren von dynamischen NPCs
default: op
nexuslobby.armorstand.lookat:
description: Erlaubt das Aktivieren des Blickkontakts bei NPCs
default: op
nexuslobby.hologram:
description: Erlaubt das Erstellen von Text-Display Hologrammen
default: op
nexuslobby.mapart:
description: Erlaubt das Erstellen von Map-Art Bildern
default: op default: op