14 Commits
1.3 ... 1.8

Author SHA1 Message Date
c7eba7d902 README.md aktualisiert 2026-03-22 23:06:08 +00:00
77c9e1fcfc README.md aktualisiert 2026-03-22 18:46:44 +00:00
4d1901c03a README.md aktualisiert 2026-03-18 17:28:10 +00:00
99a817d33d Update from Git Manager GUI 2026-03-17 10:11:41 +01:00
3322a500e1 README.md aktualisiert 2026-03-01 11:13:27 +00:00
7e197a9ac6 Upload dependency-reduced-pom.xml via GUI 2026-02-28 15:31:02 +00:00
63988befeb Update from Git Manager GUI 2026-02-28 16:31:00 +01:00
120245c2ad Upload pom.xml via GUI 2026-02-28 15:30:59 +00:00
65f9621c84 Upload dependency-reduced-pom.xml via GUI 2026-01-25 19:28:57 +00:00
68a4a670ee Update from Git Manager GUI 2026-01-25 20:28:55 +01:00
bae5e29db3 Upload pom.xml via GUI 2026-01-25 19:28:54 +00:00
137ba95a24 Dateien nach "/" hochladen 2025-08-22 11:14:57 +00:00
a1e415d6f0 src/main/resources/plugin.yml aktualisiert 2025-08-22 11:12:26 +00:00
b6b44d25c5 src/main/java/viper/MotionSensorGUI.java aktualisiert 2025-08-22 11:11:59 +00:00
14 changed files with 2972 additions and 896 deletions

509
README.md
View File

@@ -1,84 +1,503 @@
# 🔘 ButtonControl # 📖 ButtonControl Vollständige Anleitung
Ein leistungsstarkes Minecraft-Plugin zum Steuern von Türen und Redstone-Lampen über Buttons und Tageslichtsensoren. > **Version:** 1.8 · **Autor:** M_Viper · **Spigot:** [spigotmc.org/resources/127702](https://www.spigotmc.org/resources/127702/)
Ideal für smarte Beleuchtung und Türsysteme in Survival-, Citybuild- oder Roleplay-Servern.
--- ---
## 📦 Features ## 📋 Inhaltsverzeichnis
- 🎛️ **Steuer-Button** steuert beliebige Türen oder Lampen 1. [Was ist ButtonControl?](#was-ist-buttoncontrol)
- 🌞 **Tageslichtsensor-Erkennung** automatische Lampensteuerung bei Tag/Nacht 2. [Controller-Typen im Überblick](#controller-typen-im-überblick)
- 🔌 **Verbindungssystem** verknüpfe mehrere Blöcke mit einem Steuergerät 3. [Steuerbare Blöcke](#steuerbare-blöcke)
- 🧱 **Custom Rezepte** eigene Crafting-Rezepte für Steuer-Button & Tageslichtsensor 4. [Schritt-für-Schritt: Erste Schritte](#schritt-für-schritt-erste-schritte)
- 🔍 **Kommando `/bc info`** zeigt Plugin-Infos direkt im Spiel an 5. [Rezepte Controller herstellen](#rezepte--controller-herstellen)
- ✅ Vollständig kompatibel mit **Minecraft 1.21.5 1.21.8** 6. [Controller platzieren & verbinden](#controller-platzieren--verbinden)
7. [Bewegungsmelder konfigurieren](#bewegungsmelder-konfigurieren)
8. [Geheimwand (Secret Wall)](#geheimwand-secret-wall)
9. [Zeitplan konfigurieren](#zeitplan-konfigurieren)
10. [Controller umbenennen](#controller-umbenennen)
11. [Trust-System](#trust-system)
12. [Alle Befehle im Überblick](#alle-befehle-im-überblick)
13. [Berechtigungen](#berechtigungen)
14. [Konfigurationsdateien](#konfigurationsdateien)
15. [Häufige Fragen & Probleme](#häufige-fragen--probleme)
--- ---
## 🛠️ Installation ## Was ist ButtonControl?
1. Lade die neueste `.jar`-Datei herunter ButtonControl erlaubt es Spielern, **Türen, Eisentüren, Zauntore, Falltüren, Redstone- und Kupferlampen, Gitter, Creaking Heart, Spender/Werfer, Notenblöcke und Glocken** mit einem selbst hergestellten Controller zu steuern ohne Redstone-Kabel, ohne Mechanismen.
2. Lege sie in den `plugins/`-Ordner deines Servers
3. Starte oder reloade deinen Server **Mögliche Controller-Typen:**
4. Fertig! 🎉 - Holz- und Steinbuttons aller Arten
- Tageslichtsensoren (öffnen/schließen automatisch nach Tageszeit)
- Bewegungsmelder (Tripwire Hook reagiert auf Spieler **und** Mobs)
- Teppich-Sensoren (reagieren **nur** auf Spieler)
- Schilder (wandmontierte Controller)
--- ---
## 🎮 Befehle ## Controller-Typen im Überblick
| Befehl | Beschreibung | | Symbol | Controller | Auslöser | Besonderheit |
|----------------|------------------------------------------| |--------|-----------|---------|--------------|
| `/bc info` | Zeigt Informationen über das Plugin an | | 🔘 | **Steuer-Button** | Rechtsklick | Manuelles Öffnen/Schließen |
| ☀️ | **Steuer-Tageslichtsensor** | Tag/Nacht-Wechsel | Automatisch, kein Klick nötig |
| 🪝 | **Steuer-Bewegungsmelder** | Spieler + Mobs in der Nähe | Einstellbarer Radius & Verzögerung |
| 🟫 | **Steuer-Teppich** | Nur Spieler in der Nähe | Mobs lösen ihn **nicht** aus |
| 🪧 | **Steuer-Schild** | Rechtsklick | Wandmontiert, unsichtbarer Controller |
--- ---
## 🧪 Getestete Versionen ## Steuerbare Blöcke
- ✅ Minecraft: **1.21.5** **1.21.8** Folgende Blöcke können mit einem Controller verbunden werden:
- ✅ Java 17+
- ✅ Paper / Spigot / Purpur | Block | Funktion | Anmerkung |
|-------|---------|-----------|
| Alle Holztüren | Öffnen / Schließen | Inkl. Doppeltüren (beide Hälften werden automatisch erkannt) |
| **Eisentür** | Öffnen / Schließen | Kein Redstone-Signal nötig funktioniert direkt |
| Alle Holz-Falltüren | Öffnen / Schließen | |
| **Eisenfalltür** | Öffnen / Schließen | Wie Eisentür, kein Redstone nötig |
| Alle Zauntore | Öffnen / Schließen | |
| Redstone-Lampe + Kupferlampen | Ein / Ausschalten | Unterstützt normale, verwitterte und gewachste Kupferlampen |
| Creaking Heart (Knarrherz) | Aktivieren / Deaktivieren | Bleibt aktiv bis manuell ausgeschaltet |
| Gitter (`*_GRATE`) + Eisenstangen | Öffnen / Schließen | Öffnen = temporär frei (AIR), Schließen = Originalmaterial wird wiederhergestellt |
| Spender (Dispenser) | Auslösen | Kann per Zeitplan als Show laufen |
| Werfer (Dropper) | Auslösen | Kann per Zeitplan als Show laufen |
| Notenblock | Klingelton abspielen | Instrument pro Spieler einstellbar |
| Glocke | Läuten | |
--- ---
## 📐 Crafting-Rezepte ## Schritt-für-Schritt: Erste Schritte
### 🪛 Steuer-Button ### 1. Controller herstellen
Stelle einen Controller in der Werkbank her (siehe [Rezepte](#rezepte--controller-herstellen)).
| | | | ### 2. Controller platzieren
|---|---|---| Halte den hergestellten Controller in der Hand und **platziere ihn** wie einen normalen Block. Du erhältst die Nachricht: `§aController platziert.`
| | 🔘 | |
| | 🔘 | |
| | 🔘 | |
### 🌞 Steuer-Tageslichtsensor ### 3. Blöcke verbinden
Halte den Controller weiterhin **in der Hand** (nicht platziert!) und **klicke mit Rechtsklick** auf einen Zielblock (Tür, Lampe usw.). Du erhältst: `§aBlock verbunden.`
| | | | > Du kannst denselben Controller mit mehreren Blöcken verbinden einfach nacheinander alle Zielblöcke anklicken.
|---|---|---|
| | 🌞 | |
| | 🌞 | |
| | 🌞 | |
> 🔘 = Stone Button ### 4. Controller benutzen
> 🌞 = Daylight Detector Klicke mit **Rechtsklick** auf den platzierten Controller. Alle verbundenen Blöcke werden gleichzeitig umgeschaltet.
--- ---
## 👤 Autor ## Rezepte Controller herstellen
**M_Viper** Alle Rezepte folgen demselben Muster: **3× dasselbe Material in der mittleren Spalte** der Werkbank.
Plugin-Entwicklung & Idee
```
[ ] [X] [ ]
[ ] [X] [ ]
[ ] [X] [ ]
```
| Ergebnis | Zutat (X) |
|---------|---------|
| Steuer-Button (Eiche) | Eichen-Button |
| Steuer-Button (Stein) | Stein-Button |
| Steuer-Button (jede Holzart) | Entsprechender Button |
| Steuer-Tageslichtsensor | Tageslichtsensor |
| Steuer-Notenblock | Notenblock |
| Steuer-Bewegungsmelder | Tripwire Hook |
| Steuer-Schild | Eichenschild |
| Steuer-Teppich (Weiß) | Weißer Teppich |
| Steuer-Teppich (alle Farben) | Entsprechender Teppich |
> Alle 16 Teppichfarben können als Sensor verwendet werden sie verhalten sich identisch, nur die Farbe unterscheidet sich.
--- ---
## 📄 Lizenz ## Controller platzieren & verbinden
keine Angegeben ### Platzieren
1. Halte den fertigen Controller in der Hand
2. Platziere ihn wie einen normalen Block auf einer Fläche
3.`Controller platziert.`
### Verbinden
1. Halte den **nicht platzierten** Controller in der Hand
2. Klicke mit **Rechtsklick** auf einen Zielblock
3.`Block verbunden.`
### Grenzen pro Controller
| Block-Typ | Standard-Limit |
|-----------|---------------|
| Türen (inkl. Eisentür) | 20 |
| Zauntore | 20 |
| Falltüren (inkl. Eisen) | 20 |
| Redstone- und Kupferlampen | 50 |
| Spender | 20 |
| Werfer | 20 |
| Notenblöcke | 10 |
| Glocken | 5 |
> Limits können vom Server-Admin in `config.yml` angepasst werden.
### Controller abbauen
Schlage den Controller ab. Nur der **Besitzer** oder ein Admin darf ihn abbauen.
Beim Abbau werden jetzt alle zugehörigen Daten automatisch entfernt (sowohl in `data.yml` als auch in MySQL):
- Verbindungen
- Trust/Public-Status
- Zeitplan
- Bewegungsmelder-Einstellungen
- Secret-Wall-Blöcke, Delay und Animation
> ⚠️ Wenn ein verbundener Block (Tür, Lampe usw.) abgebaut wird, entfernt ButtonControl den Eintrag **automatisch** aus der Liste.
### Verbundene Blöcke anzeigen
Sieh einen platzierten Controller an (max. 5 Blöcke Entfernung) und tippe:
```
/bc list
```
Du siehst alle verbundenen Blöcke mit Typ, Koordinaten und Welt sowie den aktuellen Zeitplan.
--- ---
## 💡 Weitere Ideen? ## Bewegungsmelder konfigurieren
Wenn du Vorschläge oder Bugs hast öffne ein [Issue](https://github.com/dein-benutzername/ButtonControl/issues) oder einen Pull Request! ### Tripwire Hook (Standard-Bewegungsmelder)
- Erkennt **Spieler und Mobs** in einem einstellbaren Radius
- Öffnet verbundene Blöcke sobald jemand in der Nähe ist
- Schließt sie automatisch nach einer konfigurierbaren Verzögerung
### Teppich-Sensor
- Funktioniert genauso wie der Tripwire Hook
- Erkennt jedoch **nur Spieler** Tiere, Monster und andere Mobs lösen ihn **nicht** aus
- Ideal für Eingänge wo Tiere nicht versehentlich Türen öffnen sollen
### GUI öffnen
Klicke mit **Rechtsklick** auf einen platzierten Bewegungsmelder oder Teppich-Sensor.
```
┌─────────────────────────────┐
│ Bewegungsmelder-Einstellungen │
│ │
│ [🧭 Radius] [ ] [🕐 Verzögerung] │
│ │
│ [💚 Speichern] │
└─────────────────────────────┘
```
| Taste | Aktion |
|-------|--------|
| **Linksklick** auf Kompass | Radius +0,5 Blöcke |
| **Rechtsklick** auf Kompass | Radius 0,5 Blöcke |
| **Linksklick** auf Uhr | Verzögerung +1 Sekunde |
| **Rechtsklick** auf Uhr | Verzögerung 1 Sekunde |
| **Klick** auf Smaragd | Speichern & Schließen |
**Wertebereiche:**
- Radius: 0,5 20,0 Blöcke
- Verzögerung: 1 30 Sekunden
Secret-Wall-Verhalten mit Sensoren:
- Bewegungsmelder/Teppich kann auch eine Secret Wall öffnen
- Bei Erkennung: Wall öffnet
- Nach Ablauf der Verzögerung ohne Erkennung: Wall schließt
--- ---
## Geheimwand (Secret Wall)
Mit Secret Walls kannst du Blöcke eines Eingangs temporär „wegfahren“ lassen und automatisch wiederherstellen.
### Einrichtung
1. Controller ansehen und auswählen:
```
/bc secret select
```
2. Geheimblöcke nacheinander hinzufügen (jeweils Block ansehen):
```
/bc secret add
```
3. Optional Animation setzen:
```
/bc secret animation <instant|wave|reverse|center>
```
4. Optional Wiederherstellungszeit setzen:
```
/bc secret delay <sekunden>
```
5. Status prüfen:
```
/bc secret info
```
### Animationen
- `instant`: alle Blöcke gleichzeitig
- `wave`: der Reihe nach
- `reverse`: umgekehrte Reihenfolge
- `center`: von der Mitte nach außen (und beim Schließen außen nach innen)
Hinweis:
- Secret Walls funktionieren auch ohne normale verbundene Blöcke.
- Tageslichtsensoren und Bewegungsmelder können Secret Walls automatisch öffnen/schließen.
---
## Zeitplan konfigurieren
Der Zeitplan erlaubt es, verbundene Blöcke **automatisch zu einer bestimmten Ingame-Uhrzeit** zu öffnen und zu schließen ohne dass jemand klicken muss.
**Beispielanwendungen:**
- Dorftor öffnet automatisch morgens um 07:00, schließt abends um 19:00
- Laternenpfahl-Lampen schalten sich nachts ein, tagsüber aus
- Geschäfts-Eingang öffnet nur zu "Öffnungszeiten"
- Feuerwerk-Show startet abends automatisch und endet nachts (mit Werfern/Spendern)
### GUI öffnen
Sieh den Controller an und tippe:
```
/bc schedule
```
```
┌─────────────────────────────────┐
│ Zeitplan-Einstellungen │
│ │
│ [⏱ Delay] [⚖ Modus] [🔧 An/Aus] │
│ [🟢 Öffnungszeit] [🔴 Schließzeit] │
│ │
│ [💚 Speichern] │
└─────────────────────────────────┘
```
| Taste | Aktion |
|-------|--------|
| **Linksklick** auf Zeit-Item | +1 Stunde |
| **Rechtsklick** auf Zeit-Item | 1 Stunde |
| **Shift + Linksklick** | +15 Minuten |
| **Shift + Rechtsklick** | 15 Minuten |
| **Link/Rechtsklick** auf Delay | ±1 Tick (Shift: ±5) |
| **Klick** auf Modus | `gleichzeitig` / `nacheinander` umschalten |
| **Klick** auf Hebel/Strauch | Zeitplan ein-/ausschalten |
| **Klick** auf Smaragd | Speichern & Schließen |
> ⚠️ Die Zeiten sind **Ingame-Zeiten** (ein Minecraft-Tag = 20 Minuten Echtzeit).
> Beispiel: "07:00" = Minecraft-Sonnenaufgang, "19:00" = Sonnenuntergang.
> Hinweis zu Werfer/Spender: Wenn ein Controller einen aktiven Zeitplan hat, lösen verbundene Werfer/Spender während des Zeitfensters automatisch aus.
> - Modus `gleichzeitig`: Alle verbundenen Werfer/Spender schießen pro Zyklus zusammen.
> - Modus `nacheinander`: Pro Zyklus schießt ein Gerät, dann rotiert es zum nächsten.
> - Die Delay-Anzeige zeigt Ticks und Sekunden (z.B. `20 Ticks (1.00s)`).
**Über Mitternacht:** Zeitpläne die über Mitternacht gehen (z.B. Öffnen 22:00, Schließen 04:00) werden korrekt erkannt.
---
## Controller umbenennen
Du kannst jedem Controller einen eigenen Namen geben, der bei `/bc list` angezeigt wird.
```
/bc rename <Name>
```
**Beispiele:**
```
/bc rename Haupteingang
/bc rename Scheunentür Nordseite
/bc rename Licht Wohnraum
```
> Maximale Länge: 32 Zeichen. Leerzeichen sind erlaubt. Farbcodes mit § werden unterstützt.
---
## Trust-System
Mit dem Trust-System kannst du anderen Spielern erlauben, **deinen Controller zu benutzen** ohne dass sie ihn verwalten oder abbauen dürfen.
### Spieler hinzufügen
Sieh den Controller an und tippe:
```
/bc trust <Spielername>
```
Der Spieler darf nun den Controller benutzen (auch wenn er offline ist, wenn er vorher schon einmal auf dem Server war).
### Spieler entfernen
```
/bc untrust <Spielername>
```
### Controller öffentlich machen
```
/bc public
```
Jeder Spieler auf dem Server kann den Controller nun benutzen kein Trust nötig.
### Controller privat machen
```
/bc private
```
Nur du (und vertraute Spieler) können den Controller benutzen.
### Aktuellen Status anzeigen
```
/bc list
```
Zeigt unter anderem ob der Controller öffentlich oder privat ist.
---
## Alle Befehle im Überblick
| Befehl | Beschreibung | Berechtigung |
|--------|-------------|-------------|
| `/bc info` | Plugin-Version und Statistiken anzeigen | Jeder |
| `/bc list` | Verbundene Blöcke des angesehenen Controllers anzeigen | Besitzer / Trusted / Admin |
| `/bc rename <Name>` | Controller umbenennen (Controller ansehen) | Besitzer / Admin |
| `/bc schedule` | Zeitplan-GUI öffnen (Controller ansehen) | Besitzer / Admin |
| `/bc secret select` | Secret-Controller auswählen (alternativ Blickerkennung) | Besitzer / Admin |
| `/bc secret add` | Angesehenen Block als Geheimblock hinzufügen | Besitzer / Admin |
| `/bc secret remove` | Angesehenen Geheimblock entfernen | Besitzer / Admin |
| `/bc secret clear` | Alle Geheimblöcke des Controllers löschen | Besitzer / Admin |
| `/bc secret delay <Sekunden>` | Auto-Restore-Zeit für Secret Wall setzen | Besitzer / Admin |
| `/bc secret animation <instant\|wave\|reverse\|center>` | Secret-Wall-Animation setzen | Besitzer / Admin |
| `/bc secret info` | Secret-Wall-Konfiguration anzeigen | Besitzer / Admin |
| `/bc note <Instrument>` | Notenblock-Instrument ändern | `buttoncontrol.note` |
| `/bc trust <Spieler>` | Spieler darf Controller benutzen | Besitzer / Admin |
| `/bc untrust <Spieler>` | Berechtigung entziehen | Besitzer / Admin |
| `/bc public` | Controller für alle freigeben | Besitzer / Admin |
| `/bc private` | Controller nur für Besitzer & Trusted | Besitzer / Admin |
| `/bc reload` | Konfiguration neu laden | `buttoncontrol.reload` |
### Verfügbare Instrumente für `/bc note`
```
PIANO BASS_DRUM SNARE_DRUM STICKS
BASS_GUITAR FLUTE BELL CHIME
GUITAR XYLOPHONE IRON_XYLOPHONE COW_BELL
DIDGERIDOO BIT BANJO PLING
```
**Beispiel:**
```
/bc note FLUTE
```
---
## Berechtigungen
| Permission | Beschreibung | Standard |
|-----------|-------------|---------|
| `buttoncontrol.admin` | Zugriff auf **alle** Controller (Bypass) | OP |
| `buttoncontrol.reload` | `/bc reload` ausführen | OP |
| `buttoncontrol.note` | Instrument mit `/bc note` ändern | Alle |
| `buttoncontrol.trust` | Trust-System verwenden | Alle |
| `buttoncontrol.update` | Update-Benachrichtigungen erhalten | OP |
### Admin-Bypass (`buttoncontrol.admin`)
Admins mit dieser Berechtigung können:
- Jeden Controller benutzen (auch private)
- Jeden Controller verwalten (trust, rename, schedule, public/private)
- Jeden Controller abbauen
- Alle verbundenen Blöcke einsehen (`/bc list`)
---
## Konfigurationsdateien
### `config.yml` Hauptkonfiguration
```yaml
# Maximale Anzahl verbundener Blöcke pro Controller
max-doors: 20 # Holz- und Eisentüren
max-lamps: 50 # Redstone-Lampen
max-noteblocks: 10 # Notenblöcke
max-gates: 20 # Zauntore
max-trapdoors: 20 # Holz- und Eisenfalltüren
max-bells: 5 # Glocken
max-dispensers: 20 # Spender
max-droppers: 20 # Werfer
# Notenblock-Einstellungen
default-note: "PIANO" # Standard-Instrument
double-note-enabled: true # Zweiter Ton aktiviert
double-note-delay-ms: 1000 # Abstand zwischen den Tönen (ms)
# Bewegungsmelder-Standardwerte (überschreibbar per GUI pro Sensor)
motion-detection-radius: 5.0 # Erkennungsradius in Blöcken
motion-close-delay-ms: 5000 # Verzögerung vor dem Schließen (ms)
motion-trigger-cooldown-ms: 2000 # Mindestzeit zwischen zwei Auslösungen
timed-container-interval-ticks: 40 # Legacy-Fallback für alte Zeitpläne ohne gespeicherten Delay-Wert
timed-container-shot-delay-ticks: 2 # Standard-Delay zwischen Schüssen im Zeitplan
timed-container-trigger-mode: simultaneous # Standardmodus: simultaneous oder sequential
# Sounds beim Öffnen/Schließen
sounds:
enabled: true
door-open: BLOCK_WOODEN_DOOR_OPEN
door-close: BLOCK_WOODEN_DOOR_CLOSE
iron-door-open: BLOCK_IRON_DOOR_OPEN
iron-door-close: BLOCK_IRON_DOOR_CLOSE
lamp-on: BLOCK_LEVER_CLICK
lamp-off: BLOCK_LEVER_CLICK
```
### `lang.yml` Nachrichten anpassen
Alle Spielernachrichten können in `lang.yml` geändert werden. Farbcodes mit `§` sind überall unterstützt.
```yaml
tueren-geoeffnet: "§aTüren wurden geöffnet."
controller-platziert: "§aController platziert."
# ... usw.
```
### `data.yml` Spielerdaten
Diese Datei wird **automatisch** verwaltet und sollte nicht manuell bearbeitet werden. Sie enthält alle Controller-Positionen, Verbindungen, Trust-Einstellungen, Zeitpläne, Bewegungsmelder-Settings und Secret-Wall-Daten.
---
## Häufige Fragen & Probleme
**❓ Ich habe einen Controller platziert, aber beim Klicken passiert nichts.**
→ Du musst erst Blöcke verbinden. Halte den Controller **in der Hand** (nicht den platzierten Block anklicken) und klicke auf Türen, Lampen usw.
**❓ Ich kann den Controller eines anderen Spielers nicht benutzen.**
→ Der Controller ist privat. Bitte den Besitzer, dich per `/bc trust <DeinName>` hinzuzufügen oder den Controller mit `/bc public` zu öffnen.
**❓ Ich kann den Controller nicht abbauen.**
→ Nur der Besitzer (oder ein Admin mit `buttoncontrol.admin`) darf einen Controller abbauen.
**❓ Die Eisentür öffnet sich nicht.**
→ Stelle sicher, dass die Eisentür tatsächlich mit dem Controller verbunden ist. Halte den Controller in der Hand und klicke auf die Eisentür (untere Hälfte). Die obere Hälfte wird automatisch mitgenommen.
**❓ Der Bewegungsmelder schließt nicht nach der Zeit.**
→ Prüfe den Wert `motion-close-delay-ms` in der GUI (Rechtsklick auf den Sensor) oder in `config.yml`. Standardmäßig sind es 5 Sekunden.
**❓ Der Teppich-Sensor reagiert auf Mobs.**
→ Nur der **Steuer-Teppich** (hergestellt mit dem Rezept) erkennt nur Spieler. Ein normaler Teppich ist kein Controller.
**❓ Der Zeitplan funktioniert nicht.**
→ Stelle sicher, dass der Zeitplan in der ScheduleGUI **aktiviert** ist (grüner Hebel, nicht Strauch). Öffne die GUI mit `/bc schedule` und prüfe den Ein/Aus-Status.
**❓ Werfer/Spender schießen zu langsam oder zu schnell im Zeitplan.**
→ Stelle den Delay direkt in `/bc schedule` ein (GUI zeigt Ticks + Sekunden). Für globale Standardwerte passe `timed-container-shot-delay-ticks` in `config.yml` an und führe `/bc reload` aus.
**❓ 2 Werfer/Spender am selben Controller laufen nicht gleich.**
→ Öffne `/bc schedule` und stelle den Modus auf `gleichzeitig`. Im Modus `nacheinander` rotieren die Geräte absichtlich.
**`/bc list` zeigt "Keine Blöcke verbunden".**
→ Entweder wurde der Controller noch nie mit Blöcken verbunden, oder alle verbundenen Blöcke wurden abgebaut (werden automatisch entfernt).
**❓ Wie sehe ich ob ein Controller einen Zeitplan hat?**
`/bc list` zeigt ganz unten den aktiven Zeitplan mit Öffnungs- und Schließzeit an.
**❓ Kann ich mehrere Controller auf dieselbe Tür zeigen lassen?**
→ Ja. Du kannst z.B. einen Button-Controller und einen Bewegungsmelder mit derselben Tür verbinden.
---
*Diese Anleitung bezieht sich auf ButtonControl v1.8. Für ältere Versionen können einzelne Funktionen abweichen.*

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>viper</groupId>
<artifactId>ButtonControl</artifactId>
<version>1.4</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- bStats Monitoring -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>de.viper.bstats</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

174
pom.xml
View File

@@ -1,87 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"> http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>viper</groupId> <groupId>viper</groupId>
<artifactId>ButtonControl</artifactId> <artifactId>ButtonControl</artifactId>
<version>1.3</version> <version>1.6</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>ButtonControl</name> <name>ButtonControl</name>
<repositories> <repositories>
<!-- Spigot-Repository --> <!-- Spigot-Repository -->
<repository> <repository>
<id>spigot-repo</id> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository> </repository>
</repositories> </repositories>
<dependencies> <dependencies>
<!-- Spigot API (bereitgestellt vom Server) --> <!-- Spigot API (bereitgestellt vom Server) -->
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.21.1-R0.1-SNAPSHOT</version> <version>1.21.1-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- bStats Bukkit --> <!-- bStats Bukkit -->
<dependency> <dependency>
<groupId>org.bstats</groupId> <groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId> <artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version> <version>3.0.2</version>
</dependency> </dependency>
<!-- org.json für UpdateChecker JSON-Parsing --> <!-- org.json für UpdateChecker JSON-Parsing -->
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20240303</version> <version>20240303</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<!-- Compiler Plugin --> <!-- Compiler Plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
<configuration> <configuration>
<source>17</source> <source>17</source>
<target>17</target> <target>17</target>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
</configuration> </configuration>
</plugin> </plugin>
<!-- Shade Plugin zum Relocaten von bStats --> <!-- Shade Plugin zum Relocaten von bStats -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version> <version>3.3.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<relocations> <relocations>
<relocation> <relocation>
<pattern>org.bstats</pattern> <pattern>org.bstats</pattern>
<shadedPattern>viper.shaded.bstats</shadedPattern> <shadedPattern>viper.shaded.bstats</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -17,85 +17,121 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.Note; import org.bukkit.Note;
import org.bukkit.Note.Tone; import org.bukkit.Note.Tone;
import org.bukkit.event.Listener; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.event.EventHandler; import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.List; import java.util.ArrayList;
import java.util.UUID; import java.util.Arrays;
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;
public class ButtonControl extends JavaPlugin { public class ButtonControl extends JavaPlugin {
private ConfigManager configManager; private ConfigManager configManager;
private DataManager dataManager; private DataManager dataManager;
private Map<String, Long> lastMotionDetections = new HashMap<>();
// Bewegungsmelder-State
private final Map<String, Long> lastMotionDetections = new HashMap<>();
private final Set<String> activeSensors = new HashSet<>();
// Zeitgesteuerte Automation verhindert mehrfaches Auslösen pro Zustandswechsel
private final Map<String, Boolean> timedControllerLastState = new HashMap<>();
// Actionbar-Status pro Spieler für die Namensanzeige
private final Map<java.util.UUID, String> lastControllerActionbar = new HashMap<>();
@Override @Override
public void onEnable() { public void onEnable() {
configManager = new ConfigManager(this); configManager = new ConfigManager(this);
dataManager = new DataManager(this); dataManager = new DataManager(this);
// Spigot Update Checker starten // Update-Checker beim Start
new UpdateChecker(this, 127702).getVersion(version -> { new UpdateChecker(this, 127702).getVersion(version -> {
String currentVersion = this.getDescription().getVersion(); String current = getDescription().getVersion();
String normalizedLatest = version.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); if (isNewerVersion(strip(version), strip(current))) {
String normalizedCurrent = currentVersion.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); getLogger().info("Update verfügbar: v" + version);
Bukkit.getScheduler().runTask(this, () ->
if (isNewerVersion(normalizedLatest, normalizedCurrent)) {
getLogger().info("Neue Version verfügbar: " + version);
getLogger().info("Download: https://www.spigotmc.org/resources/buttoncontrol.127702/");
Bukkit.getScheduler().runTask(this, () -> {
Bukkit.getOnlinePlayers().stream() Bukkit.getOnlinePlayers().stream()
.filter(p -> p.hasPermission("buttoncontrol.update")) .filter(p -> p.hasPermission("buttoncontrol.update"))
.forEach(p -> { .forEach(p -> sendUpdateMessage(p, current, version)));
p.sendMessage("§6[ButtonControl] §eEine neue Version ist verfügbar: §f" + version);
p.sendMessage("§6[ButtonControl] §eDownload: §fhttps://www.spigotmc.org/resources/buttoncontrol.127702/");
});
});
} else { } else {
getLogger().info("Keine neue Version verfügbar."); getLogger().info("ButtonControl ist auf dem neuesten Stand (v" + current + ").");
} }
}); });
// Listener für Spieler-Joins // Update beim Joinen
getServer().getPluginManager().registerEvents(new Listener() { getServer().getPluginManager().registerEvents(new org.bukkit.event.Listener() {
@EventHandler @org.bukkit.event.EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission("buttoncontrol.update")) return; if (!player.hasPermission("buttoncontrol.update")) return;
new UpdateChecker(ButtonControl.this, 127702).getVersion(version -> { new UpdateChecker(ButtonControl.this, 127702).getVersion(version -> {
String currentVersion = getDescription().getVersion(); String current = getDescription().getVersion();
String normalizedLatest = version.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); if (isNewerVersion(strip(version), strip(current)))
String normalizedCurrent = currentVersion.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); sendUpdateMessage(player, current, version);
if (isNewerVersion(normalizedLatest, normalizedCurrent)) {
player.sendMessage("§6[ButtonControl] §eEine neue Version ist verfügbar: §f" + version);
player.sendMessage("§6[ButtonControl] §eDownload: §fhttps://www.spigotmc.org/resources/buttoncontrol.127702/");
}
}); });
} }
}, this); }, this);
updateConfigWithDefaults(); if (getCommand("bc") != null) {
getServer().getPluginManager().registerEvents(new ButtonListener(this, configManager, dataManager), this); getCommand("bc").setExecutor(this);
getCommand("bc").setTabCompleter(new ButtonTabCompleter());
}
getServer().getPluginManager().registerEvents(
new ButtonListener(this, configManager, dataManager), this);
registerRecipes(); registerRecipes();
getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10);
getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L);
MetricsHandler.startMetrics(this); MetricsHandler.startMetrics(this);
getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10);
getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L);
getServer().getScheduler().runTaskTimer(this, this::checkTimedControllers, 0L, 20L * 5);
getServer().getScheduler().runTaskTimer(this, this::updateControllerNameActionBar, 0L, 5L);
getLogger().info("ButtonControl v" + getDescription().getVersion() + " wurde erfolgreich aktiviert!");
}
@Override
public void onDisable() {
if (dataManager != null) {
dataManager.shutdown();
}
}
// -----------------------------------------------------------------------
// Update-Hilfe
// -----------------------------------------------------------------------
private String strip(String v) {
return v.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim();
}
private void sendUpdateMessage(Player player, String current, String latest) {
player.sendMessage("");
player.sendMessage("§8§m-----------------------------------------");
player.sendMessage(" §6§lButtonControl §7- §e§lUpdate verfügbar!");
player.sendMessage("");
player.sendMessage(" §7Aktuelle Version: §c" + current);
player.sendMessage(" §7Neue Version: §a" + latest);
player.sendMessage("");
player.sendMessage(" §eDownload: §bhttps://www.spigotmc.org/resources/127702/");
player.sendMessage("§8§m-----------------------------------------");
player.sendMessage("");
} }
private boolean isNewerVersion(String latest, String current) { private boolean isNewerVersion(String latest, String current) {
try { try {
String[] latestParts = latest.split("\\."); String[] lp = latest.split("\\.");
String[] currentParts = current.split("\\."); String[] cp = current.split("\\.");
int length = Math.max(latestParts.length, currentParts.length); int len = Math.max(lp.length, cp.length);
for (int i = 0; i < len; i++) {
for (int i = 0; i < length; i++) { int l = i < lp.length ? Integer.parseInt(lp[i]) : 0;
int latestPart = (i < latestParts.length) ? Integer.parseInt(latestParts[i]) : 0; int c = i < cp.length ? Integer.parseInt(cp[i]) : 0;
int currentPart = (i < currentParts.length) ? Integer.parseInt(currentParts[i]) : 0; if (l > c) return true;
if (latestPart > currentPart) return true; if (l < c) return false;
if (latestPart < currentPart) return false;
} }
return false; return false;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@@ -103,215 +139,508 @@ public class ButtonControl extends JavaPlugin {
} }
} }
private void updateConfigWithDefaults() { // -----------------------------------------------------------------------
if (!configManager.getConfig().contains("default-note")) { // Rezepte
configManager.getConfig().set("default-note", "PIANO"); // -----------------------------------------------------------------------
}
if (!configManager.getConfig().contains("double-note-enabled")) {
configManager.getConfig().set("double-note-enabled", true);
}
if (!configManager.getConfig().contains("double-note-delay-ms")) {
configManager.getConfig().set("double-note-delay-ms", 1000);
}
if (!configManager.getConfig().contains("motion-detection-radius")) {
configManager.getConfig().set("motion-detection-radius", 5.0);
}
if (!configManager.getConfig().contains("motion-close-delay-ms")) {
configManager.getConfig().set("motion-close-delay-ms", 5000);
}
configManager.saveConfig();
}
private void registerRecipes() { private void registerRecipes() {
ItemStack controlButton = new ItemStack(Material.STONE_BUTTON); // Alle Holz/Stein-Buttons
ItemMeta buttonMeta = controlButton.getItemMeta(); for (Material mat : Material.values()) {
buttonMeta.setDisplayName("§6Steuer-Button"); if (!mat.name().endsWith("_BUTTON")) continue;
controlButton.setItemMeta(buttonMeta); registerColumnRecipe(
"control_" + mat.name().toLowerCase(), mat,
"§6Steuer-" + friendlyName(mat, "_BUTTON"),
Arrays.asList("§7Ein universeller Controller.",
"§7Verbindet Türen, Lampen und mehr.")
);
}
NamespacedKey buttonKey = new NamespacedKey(this, "control_button"); // Tageslichtsensor
ShapedRecipe buttonRecipe = new ShapedRecipe(buttonKey, controlButton); registerColumnRecipe("control_daylight", Material.DAYLIGHT_DETECTOR,
buttonRecipe.shape("123", "456", "789"); "§6Steuer-Tageslichtsensor",
buttonRecipe.setIngredient('2', Material.STONE_BUTTON); Arrays.asList("§7Öffnet/schließt nach Tageszeit."));
buttonRecipe.setIngredient('5', Material.STONE_BUTTON);
buttonRecipe.setIngredient('8', Material.STONE_BUTTON);
Bukkit.addRecipe(buttonRecipe);
ItemStack controlDaylight = new ItemStack(Material.DAYLIGHT_DETECTOR); // Notenblock
ItemMeta daylightMeta = controlDaylight.getItemMeta(); registerColumnRecipe("control_noteblock", Material.NOTE_BLOCK,
daylightMeta.setDisplayName("§6Steuer-Tageslichtsensor"); "§6Steuer-Notenblock",
controlDaylight.setItemMeta(daylightMeta); Arrays.asList("§7Spielt einen Klingelton ab."));
NamespacedKey daylightKey = new NamespacedKey(this, "control_daylight"); // Bewegungsmelder
ShapedRecipe daylightRecipe = new ShapedRecipe(daylightKey, controlDaylight); registerColumnRecipe("control_motion", Material.TRIPWIRE_HOOK,
daylightRecipe.shape("123", "456", "789"); "§6Steuer-Bewegungsmelder",
daylightRecipe.setIngredient('2', Material.DAYLIGHT_DETECTOR); Arrays.asList("§7Erkennt Spieler und Mobs in der Nähe."));
daylightRecipe.setIngredient('5', Material.DAYLIGHT_DETECTOR);
daylightRecipe.setIngredient('8', Material.DAYLIGHT_DETECTOR);
Bukkit.addRecipe(daylightRecipe);
ItemStack controlNoteBlock = new ItemStack(Material.NOTE_BLOCK); // NEU: Schild-Controller
ItemMeta noteBlockMeta = controlNoteBlock.getItemMeta(); registerColumnRecipe("control_sign", Material.OAK_SIGN,
noteBlockMeta.setDisplayName("§6Steuer-Notenblock"); "§6Steuer-Schild",
controlNoteBlock.setItemMeta(noteBlockMeta); Arrays.asList("§7Wandmontierbarer Controller.",
"§7Funktioniert wie ein Button."));
NamespacedKey noteBlockKey = new NamespacedKey(this, "control_noteblock"); // NEU: Teppich-Sensoren (alle 16 Farben) NUR Spieler
ShapedRecipe noteBlockRecipe = new ShapedRecipe(noteBlockKey, controlNoteBlock); for (Material mat : Material.values()) {
noteBlockRecipe.shape("123", "456", "789"); if (!mat.name().endsWith("_CARPET")) continue;
noteBlockRecipe.setIngredient('2', Material.NOTE_BLOCK); registerColumnRecipe(
noteBlockRecipe.setIngredient('5', Material.NOTE_BLOCK); "control_carpet_" + mat.name().toLowerCase(), mat,
noteBlockRecipe.setIngredient('8', Material.NOTE_BLOCK); "§6Steuer-Teppich §8(" + friendlyName(mat, "_CARPET") + "§8)",
Bukkit.addRecipe(noteBlockRecipe); Arrays.asList("§7Erkennt NUR Spieler (keine Mobs).",
"§7Bodenbasierter Bewegungsmelder.")
ItemStack controlMotion = new ItemStack(Material.TRIPWIRE_HOOK); );
ItemMeta motionMeta = controlMotion.getItemMeta(); }
motionMeta.setDisplayName("§6Steuer-Bewegungsmelder");
controlMotion.setItemMeta(motionMeta);
NamespacedKey motionKey = new NamespacedKey(this, "control_motion");
ShapedRecipe motionRecipe = new ShapedRecipe(motionKey, controlMotion);
motionRecipe.shape("123", "456", "789");
motionRecipe.setIngredient('2', Material.TRIPWIRE_HOOK);
motionRecipe.setIngredient('5', Material.TRIPWIRE_HOOK);
motionRecipe.setIngredient('8', Material.TRIPWIRE_HOOK);
Bukkit.addRecipe(motionRecipe);
} }
private void registerColumnRecipe(String keyName, Material mat, String displayName, List<String> lore) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(displayName);
meta.setLore(new ArrayList<>(lore));
item.setItemMeta(meta);
}
NamespacedKey key = new NamespacedKey(this, keyName);
if (Bukkit.getRecipe(key) != null) Bukkit.removeRecipe(key);
ShapedRecipe recipe = new ShapedRecipe(key, item);
recipe.shape(" X ", " X ", " X ");
recipe.setIngredient('X', mat);
Bukkit.addRecipe(recipe);
}
/** IRON_DOOR → "§7Iron Door" | OAK_BUTTON → "§7Oak Button" */
String friendlyName(Material mat, String stripSuffix) {
String[] parts = mat.name().replace(stripSuffix, "").split("_");
StringBuilder sb = new StringBuilder("§7");
for (String p : parts) {
if (sb.length() > 2) sb.append(" ");
sb.append(Character.toUpperCase(p.charAt(0)))
.append(p.substring(1).toLowerCase());
}
return sb.toString();
}
// -----------------------------------------------------------------------
// Tageslichtsensor
// -----------------------------------------------------------------------
public void checkDaylightSensors() { public void checkDaylightSensors() {
List<String> allControllers = dataManager.getAllPlacedControllers(); for (String loc : dataManager.getAllPlacedControllers()) {
for (String controllerLoc : allControllers) { String buttonId = dataManager.getButtonIdForPlacedController(loc);
String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc);
if (buttonId == null) continue; if (buttonId == null) continue;
Location location = parseLocation(loc);
if (location == null) continue;
if (location.getBlock().getType() != Material.DAYLIGHT_DETECTOR) continue;
String[] parts = controllerLoc.split(","); long time = location.getWorld().getTime();
if (parts.length != 4) continue;
World world = getServer().getWorld(parts[0]);
if (world == null) continue;
Location loc = new Location(world,
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Integer.parseInt(parts[3]));
Block block = loc.getBlock();
if (block.getType() != Material.DAYLIGHT_DETECTOR) continue;
long time = loc.getWorld().getTime();
boolean isDay = time >= 0 && time < 13000; boolean isDay = time >= 0 && time < 13000;
List<String> connected = dataManager.getConnectedBlocks(buttonId);
if (connected == null) continue;
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId); for (String ts : connected) {
if (connectedBlocks == null) continue; Location tl = parseLocation(ts);
if (tl == null) continue;
for (String targetLocStr : connectedBlocks) { Block tb = tl.getBlock();
String[] targetParts = targetLocStr.split(","); if (tb.getType() == Material.REDSTONE_LAMP) {
if (targetParts.length != 4) continue; Lightable lamp = (Lightable) tb.getBlockData();
World targetWorld = getServer().getWorld(targetParts[0]);
if (targetWorld == null) continue;
Location targetLoc = new Location(targetWorld,
Integer.parseInt(targetParts[1]),
Integer.parseInt(targetParts[2]),
Integer.parseInt(targetParts[3]));
Block targetBlock = targetLoc.getBlock();
if (targetBlock.getType() == Material.REDSTONE_LAMP) {
Lightable lamp = (Lightable) targetBlock.getBlockData();
lamp.setLit(!isDay); lamp.setLit(!isDay);
targetBlock.setBlockData(lamp); tb.setBlockData(lamp);
} }
} }
} }
} }
// -----------------------------------------------------------------------
// Zeitgesteuerte Automation (NEU)
// -----------------------------------------------------------------------
/**
* Prüft alle 5 Sekunden ob ein Zeitplan (open-time / close-time) für einen Controller
* aktiv ist und öffnet/schließt die verbundenen Blöcke bei Wechsel.
*
* Ingame-Zeit: 0 = Sonnenaufgang, 6000 = Mittag, 13000 = Sonnenuntergang, 18000 = Mitternacht
* Anzeige: ticksToTime() wandelt in "HH:MM" um (Tag beginnt um 06:00).
*/
public void checkTimedControllers() {
for (String controllerLoc : dataManager.getAllPlacedControllers()) {
String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc);
if (buttonId == null) continue;
long openTime = dataManager.getScheduleOpenTime(buttonId);
long closeTime = dataManager.getScheduleCloseTime(buttonId);
if (openTime < 0 || closeTime < 0) continue;
Location loc = parseLocation(controllerLoc);
if (loc == null) continue;
long worldTime = loc.getWorld().getTime() % 24000;
boolean shouldBeOpen;
if (openTime <= closeTime) {
// Normales Intervall: z.B. öffnen 6000, schließen 18000
shouldBeOpen = worldTime >= openTime && worldTime < closeTime;
} else {
// Über Mitternacht: z.B. öffnen 20000, schließen 4000
shouldBeOpen = worldTime >= openTime || worldTime < closeTime;
}
Boolean lastState = timedControllerLastState.get(controllerLoc);
if (lastState != null && lastState == shouldBeOpen) continue;
timedControllerLastState.put(controllerLoc, shouldBeOpen);
List<String> connected = dataManager.getConnectedBlocks(buttonId);
if (connected != null && !connected.isEmpty()) {
setOpenables(connected, shouldBeOpen);
}
}
}
// -----------------------------------------------------------------------
// Controller-Name bei Blickkontakt (Actionbar)
// -----------------------------------------------------------------------
private void updateControllerNameActionBar() {
if (!configManager.getConfig().getBoolean("controller-name-display.enabled", true)) {
clearAllControllerActionBars();
return;
}
int maxDistance = Math.max(1,
configManager.getConfig().getInt("controller-name-display.max-look-distance", 8));
String format = configManager.getConfig().getString(
"controller-name-display.format", "§6Controller: §f%s");
for (Player player : getServer().getOnlinePlayers()) {
String message = null;
Block target = player.getTargetBlockExact(maxDistance);
if (isValidController(target)) {
String targetLoc = toLoc(target);
String buttonId = dataManager.getButtonIdForLocation(targetLoc);
if (buttonId != null) {
boolean canSee = dataManager.canAccess(buttonId, player.getUniqueId())
|| player.hasPermission("buttoncontrol.admin");
String name = dataManager.getControllerName(buttonId);
if (canSee && name != null && !name.trim().isEmpty()) {
message = String.format(format, name);
}
}
}
java.util.UUID uuid = player.getUniqueId();
String previous = lastControllerActionbar.get(uuid);
if (message == null) {
if (previous != null) {
sendActionBar(player, " ");
lastControllerActionbar.remove(uuid);
}
} else if (!message.equals(previous)) {
sendActionBar(player, message);
lastControllerActionbar.put(uuid, message);
}
}
}
private void clearAllControllerActionBars() {
if (lastControllerActionbar.isEmpty()) return;
for (Player player : getServer().getOnlinePlayers()) {
if (lastControllerActionbar.containsKey(player.getUniqueId())) {
sendActionBar(player, " ");
}
}
lastControllerActionbar.clear();
}
private void sendActionBar(Player player, String message) {
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
}
// -----------------------------------------------------------------------
// Bewegungsmelder
// -----------------------------------------------------------------------
public void checkMotionSensors() { public void checkMotionSensors() {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<String> allControllers = dataManager.getAllPlacedControllers();
for (String controllerLoc : allControllers) {
String[] parts = controllerLoc.split(",");
if (parts.length != 4) continue;
World world = getServer().getWorld(parts[0]); for (String controllerLoc : dataManager.getAllPlacedControllers()) {
if (world == null) continue; Location loc = parseLocation(controllerLoc);
if (loc == null) continue;
Material bType = loc.getBlock().getType();
Location loc = new Location(world, boolean isTripwire = bType == Material.TRIPWIRE_HOOK;
Integer.parseInt(parts[1]), boolean isCarpet = bType.name().endsWith("_CARPET");
Integer.parseInt(parts[2]), if (!isTripwire && !isCarpet) continue;
Integer.parseInt(parts[3]));
Block block = loc.getBlock();
if (block.getType() != Material.TRIPWIRE_HOOK) continue;
String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc); String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc);
if (buttonId == null) continue; if (buttonId == null) continue;
// Individuelle Einstellungen für diesen Bewegungsmelder
double radius = dataManager.getMotionSensorRadius(controllerLoc); double radius = dataManager.getMotionSensorRadius(controllerLoc);
if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0); if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0);
long delay = dataManager.getMotionSensorDelay(controllerLoc); long delay = dataManager.getMotionSensorDelay(controllerLoc);
if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L); if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L);
boolean detected = !world.getNearbyEntities(loc, radius, radius, radius, e -> e instanceof Player).isEmpty(); final double r = radius;
boolean detected;
if (isCarpet) {
// NEU: Teppich erkennt NUR Spieler
detected = !loc.getWorld()
.getNearbyEntities(loc, r, r, r, e -> e instanceof Player).isEmpty();
} else {
// Tripwire: alle lebenden Entitäten
detected = !loc.getWorld()
.getNearbyEntities(loc, r, r, r,
e -> e instanceof org.bukkit.entity.LivingEntity).isEmpty();
}
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId); List<String> connected = dataManager.getConnectedBlocks(buttonId);
if (connectedBlocks == null || connectedBlocks.isEmpty()) continue; if (connected == null || connected.isEmpty()) continue;
if (detected) { if (detected) {
setOpenables(connectedBlocks, true); if (!activeSensors.contains(controllerLoc)) {
setOpenables(connected, true);
activeSensors.add(controllerLoc);
}
lastMotionDetections.put(controllerLoc, now); lastMotionDetections.put(controllerLoc, now);
} else { } else {
Long last = lastMotionDetections.get(controllerLoc); Long last = lastMotionDetections.get(controllerLoc);
if (last != null && now - last >= delay) { if (last != null && now - last >= delay) {
setOpenables(connectedBlocks, false); setOpenables(connected, false);
lastMotionDetections.remove(controllerLoc); lastMotionDetections.remove(controllerLoc);
activeSensors.remove(controllerLoc);
} }
} }
} }
} }
private void setOpenables(List<String> connectedBlocks, boolean open) { void setOpenables(List<String> connectedBlocks, boolean open) {
for (String targetLocStr : connectedBlocks) { for (String locStr : connectedBlocks) {
String[] targetParts = targetLocStr.split(","); Location tl = parseLocation(locStr);
if (targetParts.length != 4) continue; if (tl == null) continue;
Block tb = tl.getBlock();
World targetWorld = getServer().getWorld(targetParts[0]); if (tb.getBlockData() instanceof org.bukkit.block.data.Openable) {
if (targetWorld == null) continue; org.bukkit.block.data.Openable o = (org.bukkit.block.data.Openable) tb.getBlockData();
o.setOpen(open);
Location targetLoc = new Location(targetWorld, tb.setBlockData(o);
Integer.parseInt(targetParts[1]),
Integer.parseInt(targetParts[2]),
Integer.parseInt(targetParts[3]));
Block targetBlock = targetLoc.getBlock();
if (targetBlock.getBlockData() instanceof org.bukkit.block.data.Openable) {
org.bukkit.block.data.Openable openable = (org.bukkit.block.data.Openable) targetBlock.getBlockData();
openable.setOpen(open);
targetBlock.setBlockData(openable);
} }
} }
} }
// -----------------------------------------------------------------------
// Befehle
// -----------------------------------------------------------------------
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!command.getName().equalsIgnoreCase("bc")) return false;
if (args.length == 0) {
sender.sendMessage("§6[BC] §7/bc <info|reload|note|list|rename|schedule|trust|untrust|public|private>");
return true;
}
String sub = args[0].toLowerCase();
// INFO
if (sub.equals("info")) {
sender.sendMessage("§6§lButtonControl §7v" + getDescription().getVersion() + " §8by §7M_Viper");
sender.sendMessage("§7Features: §fTüren · Lampen · Notenblöcke · Sensoren · Teppiche · Schilder · Zeitpläne");
sender.sendMessage("§7Controller aktiv: §f" + dataManager.getAllPlacedControllers().size());
return true;
}
// RELOAD
if (sub.equals("reload")) {
if (!sender.hasPermission("buttoncontrol.reload")) {
sender.sendMessage(configManager.getMessage("keine-berechtigung")); return true;
}
configManager.reloadConfig();
dataManager.reloadData();
timedControllerLastState.clear();
clearAllControllerActionBars();
sender.sendMessage(configManager.getMessage("konfiguration-neugeladen"));
return true;
}
// NOTE
if (sub.equals("note") && sender instanceof Player) {
Player player = (Player) sender;
if (args.length < 2) { player.sendMessage("§7/bc note <Instrument>"); return true; }
try {
org.bukkit.Instrument.valueOf(args[1].toUpperCase());
dataManager.setPlayerInstrument(player.getUniqueId(), args[1].toUpperCase());
player.sendMessage(String.format(configManager.getMessage("instrument-gesetzt"), args[1].toUpperCase()));
} catch (Exception e) {
player.sendMessage(configManager.getMessage("ungueltiges-instrument"));
}
return true;
}
// Alle folgenden Befehle erfordern Spieler + angescauten Controller
if (!(sender instanceof Player)) {
sender.sendMessage("§cNur Spieler können diesen Befehl verwenden.");
return true;
}
Player player = (Player) sender;
if (sub.equals("list") || sub.equals("rename") || sub.equals("schedule")
|| sub.equals("trust") || sub.equals("untrust")
|| sub.equals("public") || sub.equals("private")) {
Block target = player.getTargetBlockExact(5);
if (!isValidController(target)) {
player.sendMessage(configManager.getMessage("kein-controller-im-blick")); return true;
}
String targetLoc = toLoc(target);
String buttonId = dataManager.getButtonIdForLocation(targetLoc);
if (buttonId == null) {
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden")); return true;
}
boolean isAdmin = player.hasPermission("buttoncontrol.admin");
boolean isOwner = dataManager.isOwner(buttonId, player.getUniqueId());
switch (sub) {
case "list":
if (!dataManager.canAccess(buttonId, player.getUniqueId()) && !isAdmin) {
player.sendMessage(configManager.getMessage("keine-berechtigung-controller")); return true;
}
sendListInfo(player, buttonId);
break;
case "rename":
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
if (args.length < 2) { player.sendMessage("§7/bc rename <Name>"); return true; }
String newName = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (newName.length() > 32) { player.sendMessage("§cName zu lang (max. 32 Zeichen)."); return true; }
dataManager.setControllerName(buttonId, newName);
player.sendMessage(String.format(configManager.getMessage("controller-umbenannt"), newName));
break;
case "schedule":
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
new ScheduleGUI(this, player, buttonId).open();
break;
case "trust":
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
if (args.length < 2) { player.sendMessage("§7/bc trust <Spieler>"); return true; }
org.bukkit.OfflinePlayer tp = Bukkit.getOfflinePlayer(args[1]);
if (!tp.hasPlayedBefore() && !tp.isOnline()) {
player.sendMessage(configManager.getMessage("spieler-nicht-gefunden")); return true;
}
dataManager.addTrustedPlayer(buttonId, tp.getUniqueId());
player.sendMessage(String.format(configManager.getMessage("trust-hinzugefuegt"), args[1]));
break;
case "untrust":
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
if (args.length < 2) { player.sendMessage("§7/bc untrust <Spieler>"); return true; }
dataManager.removeTrustedPlayer(buttonId, Bukkit.getOfflinePlayer(args[1]).getUniqueId());
player.sendMessage(String.format(configManager.getMessage("trust-entfernt"), args[1]));
break;
default: // public / private
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
boolean pub = sub.equals("public");
dataManager.setPublic(buttonId, pub);
player.sendMessage(String.format(configManager.getMessage("status-geandert"),
pub ? "§aÖffentlich" : "§cPrivat"));
break;
}
}
return true;
}
private void sendListInfo(Player player, String buttonId) {
String name = dataManager.getControllerName(buttonId);
String header = name != null
? "§6§l" + name
: "§6§lController §8§o(ID: " + buttonId.substring(0, 8) + "...)";
player.sendMessage(header);
List<String> connected = dataManager.getConnectedBlocks(buttonId);
if (connected == null || connected.isEmpty()) {
player.sendMessage(" §cKeine Blöcke verbunden.");
} else {
player.sendMessage("§7Verbundene Blöcke §8(" + connected.size() + ")§7:");
for (int i = 0; i < connected.size(); i++) {
String ls = connected.get(i);
Location l = parseLocation(ls);
String typeLabel = l != null ? "§e" + l.getBlock().getType().name() : "§8unbekannt";
String[] p = ls.split(",");
String coords = p.length == 4
? "§8(" + p[1] + "§7, §8" + p[2] + "§7, §8" + p[3] + " §7in §f" + p[0] + "§8)" : "";
player.sendMessage(" §8" + (i + 1) + ". " + typeLabel + " " + coords);
}
}
player.sendMessage("§7Status: " + (dataManager.isPublic(buttonId) ? "§aÖffentlich" : "§cPrivat"));
long openT = dataManager.getScheduleOpenTime(buttonId);
long closeT = dataManager.getScheduleCloseTime(buttonId);
if (openT >= 0 && closeT >= 0) {
player.sendMessage("§7Zeitplan: §aÖffnet §7um §e" + ticksToTime(openT)
+ " §7· §cSchließt §7um §e" + ticksToTime(closeT));
} else {
player.sendMessage("§7Zeitplan: §8Nicht gesetzt §7(§e/bc schedule§7)");
}
}
// -----------------------------------------------------------------------
// Utility
// -----------------------------------------------------------------------
/** Wandelt Minecraft-Ticks (023999) in "HH:MM" um. Ingame-Tag startet um 06:00. */
public String ticksToTime(long ticks) {
long shifted = (ticks + 6000) % 24000;
long hours = shifted / 1000;
long minutes = (shifted % 1000) * 60 / 1000;
return String.format("%02d:%02d", hours, minutes);
}
/** Wandelt "HH:MM" zurück in Minecraft-Ticks */
public long timeToTicks(int hours, int minutes) {
long totalMinutes = hours * 60L + minutes;
long ticks = (totalMinutes * 1000L / 60L - 6000 + 24000) % 24000;
return ticks;
}
public boolean isValidController(Block b) {
if (b == null) return false;
Material m = b.getType();
return m.name().endsWith("_BUTTON")
|| m == Material.DAYLIGHT_DETECTOR
|| m == Material.TRIPWIRE_HOOK
|| m.name().endsWith("_SIGN")
|| m.name().endsWith("_CARPET");
}
public String toLoc(Block b) {
return b.getWorld().getName() + "," + b.getX() + "," + b.getY() + "," + b.getZ();
}
public Location parseLocation(String locStr) {
String[] parts = locStr.split(",");
if (parts.length != 4) return null;
World world = getServer().getWorld(parts[0]);
if (world == null) return null;
try {
return new Location(world,
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Integer.parseInt(parts[3]));
} catch (NumberFormatException e) { return null; }
}
public void playDoorbellSound(Location loc, String instrument) { public void playDoorbellSound(Location loc, String instrument) {
Block block = loc.getBlock(); Block block = loc.getBlock();
if (block.getType() != Material.NOTE_BLOCK) return; if (block.getType() != Material.NOTE_BLOCK) return;
NoteBlock noteBlock = (NoteBlock) block.getBlockData(); NoteBlock noteBlock = (NoteBlock) block.getBlockData();
try { try {
org.bukkit.Instrument bukkitInstrument = org.bukkit.Instrument.valueOf(instrument.toUpperCase()); org.bukkit.Instrument inst = org.bukkit.Instrument.valueOf(instrument.toUpperCase());
noteBlock.setInstrument(bukkitInstrument); noteBlock.setInstrument(inst);
noteBlock.setNote(new Note(0, Tone.C, false)); noteBlock.setNote(new Note(0, Tone.C, false));
block.setBlockData(noteBlock); block.setBlockData(noteBlock);
loc.getWorld().playSound(loc, bukkitInstrument.getSound(), 1.0f, 1.0f); loc.getWorld().playSound(loc, inst.getSound(), 1.0f, 1.0f);
if (configManager.getConfig().getBoolean("double-note-enabled", true)) { if (configManager.getConfig().getBoolean("double-note-enabled", true)) {
int delayMs = configManager.getConfig().getInt("double-note-delay-ms", 1000); long delayTicks = (long)(configManager.getConfig().getInt("double-note-delay-ms", 1000) / 50.0);
long delayTicks = (long) (delayMs / 50.0);
getServer().getScheduler().runTaskLater(this, () -> { getServer().getScheduler().runTaskLater(this, () -> {
if (block.getType() == Material.NOTE_BLOCK) { if (block.getType() == Material.NOTE_BLOCK)
loc.getWorld().playSound(loc, bukkitInstrument.getSound(), 1.0f, 1.0f); loc.getWorld().playSound(loc, inst.getSound(), 1.0f, 1.0f);
}
}, delayTicks); }, delayTicks);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -319,67 +648,6 @@ public class ButtonControl extends JavaPlugin {
} }
} }
@Override public ConfigManager getConfigManager() { return configManager; }
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public DataManager getDataManager() { return dataManager; }
if (command.getName().equalsIgnoreCase("bc")) {
if (args.length == 0) {
sender.sendMessage("§6[ButtonControl] §7Verwende: /bc <info|reload|note>");
return true;
}
if (args[0].equalsIgnoreCase("info")) {
sender.sendMessage("§6[ButtonControl] §7Informationen zum Plugin:");
sender.sendMessage("§eVersion: §f" + getDescription().getVersion());
sender.sendMessage("§eErsteller: §fM_Viper");
sender.sendMessage("§ePlugin: §fButtonControl");
sender.sendMessage("§eGetestet für Minecraft: §f1.21.5 - 1.21.8");
sender.sendMessage("§eWeitere Infos: §fTüren, Lampen & Notenblöcke mit Buttons oder Tageslichtsensoren steuern");
return true;
}
if (args[0].equalsIgnoreCase("reload")) {
if (!sender.hasPermission("buttoncontrol.reload")) {
sender.sendMessage(configManager.getMessage("keine-berechtigung"));
return true;
}
configManager.reloadConfig();
updateConfigWithDefaults();
dataManager.reloadData();
sender.sendMessage(configManager.getMessage("konfiguration-reloaded"));
return true;
}
if (args[0].equalsIgnoreCase("note") && sender instanceof Player) {
Player player = (Player) sender;
if (!player.hasPermission("buttoncontrol.note")) {
player.sendMessage(configManager.getMessage("keine-berechtigung"));
return true;
}
if (args.length < 2) {
sender.sendMessage("§6[ButtonControl] §7Verwende: /bc note <Instrument>");
sender.sendMessage("§7Verfügbare Instrumente: PIANO, BASS_DRUM, SNARE, STICKS, BASS_GUITAR, FLUTE, BELL, GUITAR, CHIME, XYLOPHONE, etc.");
return true;
}
String instrument = args[1].toUpperCase();
try {
org.bukkit.Instrument.valueOf(instrument);
dataManager.setPlayerInstrument(player.getUniqueId(), instrument);
sender.sendMessage(String.format(configManager.getMessage("instrument-gesetzt"), instrument));
} catch (IllegalArgumentException e) {
sender.sendMessage(configManager.getMessage("ungueltiges-instrument"));
}
return true;
}
}
return false;
}
public ConfigManager getConfigManager() {
return configManager;
}
public DataManager getDataManager() {
return dataManager;
}
} }

View File

@@ -1,16 +1,21 @@
package viper; package viper;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.Openable; import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Lightable; import org.bukkit.block.data.Lightable;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
@@ -24,253 +29,410 @@ public class ButtonListener implements Listener {
private final DataManager dataManager; private final DataManager dataManager;
public ButtonListener(ButtonControl plugin, ConfigManager configManager, DataManager dataManager) { public ButtonListener(ButtonControl plugin, ConfigManager configManager, DataManager dataManager) {
this.plugin = plugin; this.plugin = plugin;
this.configManager = configManager; this.configManager = configManager;
this.dataManager = dataManager; this.dataManager = dataManager;
} }
// -----------------------------------------------------------------------
// Interact Benutzung + Verbinden
// -----------------------------------------------------------------------
@EventHandler @EventHandler
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
String playerUUID = event.getPlayer().getUniqueId().toString(); // Doppelt-Feuern bei Items verhindern (Haupt- und Nebenhand)
ItemStack item = event.getItem(); if (event.getHand() != EquipmentSlot.HAND) return;
Block block = event.getClickedBlock();
// Block wird gesteuert (Button, Tageslichtsensor oder Bewegungsmelder) Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
ItemStack item = event.getItem();
Block block = event.getClickedBlock();
// ── 1. Bereits platzierter Controller ──────────────────────────────
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && block != null) { if (event.getAction() == Action.RIGHT_CLICK_BLOCK && block != null) {
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); String blockLocation = plugin.toLoc(block);
String buttonId = dataManager.getButtonIdForPlacedController(playerUUID, blockLocation); String buttonId = dataManager.getButtonIdForLocation(blockLocation);
if (buttonId != null) { if (buttonId != null) {
event.setCancelled(true); // Admin-Bypass
// Bewegungsmelder: GUI öffnen if (!dataManager.canAccess(buttonId, playerUUID)
if (block.getType() == Material.TRIPWIRE_HOOK) { && !player.hasPermission("buttoncontrol.admin")) {
new MotionSensorGUI(plugin, event.getPlayer(), blockLocation, buttonId).open(); player.sendMessage(configManager.getMessage("keine-berechtigung-controller"));
event.setCancelled(true);
return; return;
} }
// Button oder Tageslichtsensor: Normale Steuerung
if (block.getType() == Material.STONE_BUTTON || block.getType() == Material.DAYLIGHT_DETECTOR) { // Tripwire & Teppich → MotionSensorGUI öffnen
List<String> connectedBlocks = dataManager.getConnectedBlocks(playerUUID, buttonId); if (block.getType() == Material.TRIPWIRE_HOOK
|| block.getType().name().endsWith("_CARPET")) {
event.setCancelled(true);
new MotionSensorGUI(plugin, player, blockLocation, buttonId).open();
return;
}
// Schild → nur mit Shift+Klick Blöcke toggeln,
// normaler Klick öffnet den Schildeditor (Vanilla)
if (block.getType().name().endsWith("_SIGN")
|| block.getType().name().endsWith("_BUTTON")
|| block.getType() == Material.DAYLIGHT_DETECTOR) {
if (player.isSneaking()) {
// Shift+Klick → Vanilla-Verhalten zulassen (Schildeditor etc.)
return;
}
event.setCancelled(true);
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
if (connectedBlocks != null && !connectedBlocks.isEmpty()) { if (connectedBlocks != null && !connectedBlocks.isEmpty()) {
boolean anyDoorOpened = false; toggleConnectedBlocks(player, playerUUID, connectedBlocks);
boolean anyDoorClosed = false;
boolean anyGateOpened = false;
boolean anyGateClosed = false;
boolean anyTrapOpened = false;
boolean anyTrapClosed = false;
boolean anyLampOn = false;
boolean anyLampOff = false;
boolean anyNoteBlockPlayed = false;
boolean anyBellPlayed = false;
for (String loc : connectedBlocks) {
String[] parts = loc.split(",");
Location location = new Location(plugin.getServer().getWorld(parts[0]),
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Integer.parseInt(parts[3]));
Block targetBlock = location.getBlock();
if (isDoor(targetBlock.getType()) || isGate(targetBlock.getType()) || isTrapdoor(targetBlock.getType())) {
if (targetBlock.getBlockData() instanceof Openable) {
Openable openable = (Openable) targetBlock.getBlockData();
boolean wasOpen = openable.isOpen();
openable.setOpen(!wasOpen);
targetBlock.setBlockData(openable);
if (isDoor(targetBlock.getType())) {
if (!wasOpen) anyDoorOpened = true; else anyDoorClosed = true;
} else if (isGate(targetBlock.getType())) {
if (!wasOpen) anyGateOpened = true; else anyGateClosed = true;
} else if (isTrapdoor(targetBlock.getType())) {
if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true;
}
}
} else if (targetBlock.getType() == Material.REDSTONE_LAMP) {
Lightable lamp = (Lightable) targetBlock.getBlockData();
boolean wasLit = lamp.isLit();
lamp.setLit(!wasLit);
targetBlock.setBlockData(lamp);
if (!wasLit) anyLampOn = true; else anyLampOff = true;
} else if (targetBlock.getType() == Material.NOTE_BLOCK) {
String instrument = dataManager.getPlayerInstrument(event.getPlayer().getUniqueId());
if (instrument == null) {
instrument = configManager.getConfig().getString("default-note", "PIANO");
}
plugin.playDoorbellSound(location, instrument);
anyNoteBlockPlayed = true;
} else if (targetBlock.getType() == Material.BELL) {
targetBlock.getWorld().playSound(location, org.bukkit.Sound.BLOCK_BELL_USE, 3.0f, 1.0f);
anyBellPlayed = true;
}
}
if (anyDoorOpened) event.getPlayer().sendMessage(configManager.getMessage("tueren-geoeffnet"));
if (anyDoorClosed) event.getPlayer().sendMessage(configManager.getMessage("tueren-geschlossen"));
if (anyGateOpened) event.getPlayer().sendMessage(configManager.getMessage("gates-geoeffnet"));
if (anyGateClosed) event.getPlayer().sendMessage(configManager.getMessage("gates-geschlossen"));
if (anyTrapOpened) event.getPlayer().sendMessage(configManager.getMessage("fallturen-geoeffnet"));
if (anyTrapClosed) event.getPlayer().sendMessage(configManager.getMessage("fallturen-geschlossen"));
if (anyLampOn) event.getPlayer().sendMessage(configManager.getMessage("lampen-eingeschaltet"));
if (anyLampOff) event.getPlayer().sendMessage(configManager.getMessage("lampen-ausgeschaltet"));
if (anyNoteBlockPlayed) event.getPlayer().sendMessage(configManager.getMessage("notenblock-ausgeloest"));
if (anyBellPlayed) event.getPlayer().sendMessage(configManager.getMessage("glocke-gelaeutet"));
} else { } else {
event.getPlayer().sendMessage(configManager.getMessage("keine-bloecke-verbunden")); player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
} }
} }
return; return;
} }
} }
// Verbindung herstellen // ── 2. Verbinden mit Controller-Item in der Hand ───────────────────
if (item == null || (!item.getType().equals(Material.STONE_BUTTON) && if (item == null || !item.hasItemMeta()) return;
!item.getType().equals(Material.DAYLIGHT_DETECTOR) && String displayName = item.getItemMeta().getDisplayName();
!item.getType().equals(Material.NOTE_BLOCK) && if (!displayName.contains("§6Steuer-")) return;
!item.getType().equals(Material.TRIPWIRE_HOOK))) { if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) return;
return;
}
if (!item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) {
return;
}
if (isDoor(block.getType()) || isGate(block.getType()) || isTrapdoor(block.getType()) ||
block.getType() == Material.REDSTONE_LAMP ||
block.getType() == Material.NOTE_BLOCK ||
block.getType() == Material.BELL) {
if (isInteractableTarget(block.getType())) {
event.setCancelled(true); event.setCancelled(true);
String buttonId = item.getItemMeta().hasLore() ? item.getItemMeta().getLore().get(0) : UUID.randomUUID().toString(); // Doppeltür: immer untersten Block speichern
List<String> connectedBlocks = dataManager.getConnectedBlocks(playerUUID, buttonId); Block targetBlock = getBottomDoorBlock(block);
if (connectedBlocks == null) {
connectedBlocks = new ArrayList<>();
}
int maxDoors = configManager.getMaxDoors(); ItemMeta meta = item.getItemMeta();
int maxGates = configManager.getMaxDoors(); String buttonId = extractButtonId(meta);
int maxTraps = configManager.getMaxDoors(); if (buttonId == null) {
int maxLamps = configManager.getMaxLamps(); buttonId = UUID.randomUUID().toString();
int maxNoteBlocks = configManager.getMaxNoteBlocks();
int maxBells = configManager.getMaxBells();
int doorCount = (int) connectedBlocks.stream().filter(loc -> isDoor(getMaterialFromLocation(loc))).count();
int gateCount = (int) connectedBlocks.stream().filter(loc -> isGate(getMaterialFromLocation(loc))).count();
int trapCount = (int) connectedBlocks.stream().filter(loc -> isTrapdoor(getMaterialFromLocation(loc))).count();
int lampCount = (int) connectedBlocks.stream().filter(loc -> getMaterialFromLocation(loc) == Material.REDSTONE_LAMP).count();
int noteBlockCount = (int) connectedBlocks.stream().filter(loc -> getMaterialFromLocation(loc) == Material.NOTE_BLOCK).count();
int bellCount = (int) connectedBlocks.stream().filter(loc -> getMaterialFromLocation(loc) == Material.BELL).count();
if (isDoor(block.getType()) && doorCount >= maxDoors) {
event.getPlayer().sendMessage(configManager.getMessage("max-tueren-erreicht"));
return;
}
if (isGate(block.getType()) && gateCount >= maxGates) {
event.getPlayer().sendMessage(configManager.getMessage("max-gates-erreicht"));
return;
}
if (isTrapdoor(block.getType()) && trapCount >= maxTraps) {
event.getPlayer().sendMessage(configManager.getMessage("max-fallturen-erreicht"));
return;
}
if (block.getType() == Material.REDSTONE_LAMP && lampCount >= maxLamps) {
event.getPlayer().sendMessage(configManager.getMessage("max-lampen-erreicht"));
return;
}
if (block.getType() == Material.NOTE_BLOCK && noteBlockCount >= maxNoteBlocks) {
event.getPlayer().sendMessage(configManager.getMessage("max-notenbloecke-erreicht"));
return;
}
if (block.getType() == Material.BELL && bellCount >= maxBells) {
event.getPlayer().sendMessage(configManager.getMessage("max-glocken-erreicht"));
return;
}
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ();
if (!connectedBlocks.contains(blockLocation)) {
connectedBlocks.add(blockLocation);
dataManager.setConnectedBlocks(playerUUID, buttonId, connectedBlocks);
updateButtonLore(item, buttonId); updateButtonLore(item, buttonId);
event.getPlayer().sendMessage(configManager.getMessage("block-verbunden")); }
} else {
event.getPlayer().sendMessage(configManager.getMessage("block-bereits-verbunden")); List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
if (connectedBlocks == null) connectedBlocks = new ArrayList<>();
String targetLocStr = plugin.toLoc(targetBlock);
if (connectedBlocks.contains(targetLocStr)) {
player.sendMessage(configManager.getMessage("block-bereits-verbunden"));
return;
}
if (checkLimits(player, targetBlock.getType(), connectedBlocks)) {
connectedBlocks.add(targetLocStr);
dataManager.setConnectedBlocks(playerUUID.toString(), buttonId, connectedBlocks);
player.sendMessage(configManager.getMessage("block-verbunden"));
} }
} }
} }
@EventHandler // -----------------------------------------------------------------------
public void onBlockPlace(BlockPlaceEvent event) { // Block-Break: Controller abbauen
String playerUUID = event.getPlayer().getUniqueId().toString(); // -----------------------------------------------------------------------
ItemStack item = event.getItemInHand();
Block block = event.getBlockPlaced();
if (item == null || (!item.getType().equals(Material.STONE_BUTTON) &&
!item.getType().equals(Material.DAYLIGHT_DETECTOR) &&
!item.getType().equals(Material.NOTE_BLOCK) &&
!item.getType().equals(Material.TRIPWIRE_HOOK))) {
return;
}
if (!item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) {
return;
}
String buttonId = item.getItemMeta().hasLore() ? item.getItemMeta().getLore().get(0) : UUID.randomUUID().toString();
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ();
dataManager.addPlacedController(playerUUID, blockLocation, buttonId);
event.getPlayer().sendMessage(configManager.getMessage("controller-platziert"));
}
@EventHandler @EventHandler
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
String playerUUID = event.getPlayer().getUniqueId().toString(); Block block = event.getBlock();
Block block = event.getBlock(); String blockLocation = plugin.toLoc(block);
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); String buttonId = dataManager.getButtonIdForLocation(blockLocation);
String buttonId = dataManager.getButtonIdForPlacedController(playerUUID, blockLocation);
if (buttonId != null) { if (buttonId != null) {
dataManager.removePlacedController(playerUUID, blockLocation); if (!dataManager.isOwner(buttonId, event.getPlayer().getUniqueId())
dataManager.setConnectedBlocks(playerUUID, buttonId, null); && !event.getPlayer().hasPermission("buttoncontrol.admin")) {
dataManager.removeMotionSensorSettings(blockLocation); // Entferne Bewegungsmelder-Einstellungen event.getPlayer().sendMessage(configManager.getMessage("nur-besitzer-abbauen"));
event.setCancelled(true);
return;
}
dataManager.removeController(blockLocation);
event.getPlayer().sendMessage(configManager.getMessage("controller-entfernt")); event.getPlayer().sendMessage(configManager.getMessage("controller-entfernt"));
} }
} }
private boolean isDoor(Material material) { // -----------------------------------------------------------------------
return material.toString().endsWith("_DOOR"); // Block-Break: Verbundener Block abgebaut → Eintrag bereinigen (NEU)
// -----------------------------------------------------------------------
@EventHandler
public void onConnectedBlockBreak(BlockBreakEvent event) {
Block block = event.getBlock();
if (!isInteractableTarget(block.getType())) return;
// Bei Türen normalisieren auf Unterblock
Block bottomBlock = getBottomDoorBlock(block);
String locStr = plugin.toLoc(bottomBlock);
if (dataManager.removeFromAllConnectedBlocks(locStr)) {
event.getPlayer().sendMessage(configManager.getMessage("block-verbindung-entfernt"));
}
} }
private boolean isGate(Material material) { // -----------------------------------------------------------------------
return material.toString().endsWith("_FENCE_GATE"); // Controller platzieren
// -----------------------------------------------------------------------
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
ItemStack item = event.getItemInHand();
if (item == null || !item.hasItemMeta()) return;
if (!item.getItemMeta().getDisplayName().contains("§6Steuer-")) return;
Block block = event.getBlockPlaced();
String buttonId = extractButtonId(item.getItemMeta());
if (buttonId == null) {
buttonId = UUID.randomUUID().toString();
updateButtonLore(item, buttonId);
}
dataManager.registerController(
plugin.toLoc(block), event.getPlayer().getUniqueId(), buttonId);
event.getPlayer().sendMessage(configManager.getMessage("controller-platziert"));
} }
private boolean isTrapdoor(Material material) { // -----------------------------------------------------------------------
return material.toString().endsWith("_TRAPDOOR"); // Toggle-Logik
// -----------------------------------------------------------------------
private void toggleConnectedBlocks(Player player, UUID playerUUID, List<String> connectedBlocks) {
boolean anyDoorOpened = false, anyDoorClosed = false;
boolean anyGateOpened = false, anyGateClosed = false;
boolean anyTrapOpened = false, anyTrapClosed = false;
boolean anyIronDoorOpened = false, anyIronDoorClosed = false;
boolean anyIronTrapOpened = false, anyIronTrapClosed = false;
boolean anyLampOn = false, anyLampOff = false;
boolean anyNoteBlockPlayed = false;
boolean anyBellPlayed = false;
boolean soundsEnabled = configManager.getConfig().getBoolean("sounds.enabled", true);
for (String locStr : connectedBlocks) {
Location location = parseLocation(locStr);
if (location == null) continue;
Block targetBlock = location.getBlock();
Material mat = targetBlock.getType();
// ── Eisentür (NEU) ──────────────────────────────────────────────
// Eisentüren implementieren Openable in der Bukkit-API.
// Wir setzen den Zustand direkt kein Redstone-Signal nötig.
if (mat == Material.IRON_DOOR) {
if (targetBlock.getBlockData() instanceof Openable) {
Openable op = (Openable) targetBlock.getBlockData();
boolean wasOpen = op.isOpen();
op.setOpen(!wasOpen);
targetBlock.setBlockData(op);
if (soundsEnabled) {
String soundKey = wasOpen ? "sounds.iron-door-close" : "sounds.iron-door-open";
playConfigSound(location, soundKey,
wasOpen ? "BLOCK_IRON_DOOR_CLOSE" : "BLOCK_IRON_DOOR_OPEN");
}
if (!wasOpen) anyIronDoorOpened = true; else anyIronDoorClosed = true;
}
continue;
}
// ── Eisenfalltür (NEU) ─────────────────────────────────────────
if (mat == Material.IRON_TRAPDOOR) {
if (targetBlock.getBlockData() instanceof Openable) {
Openable op = (Openable) targetBlock.getBlockData();
boolean wasOpen = op.isOpen();
op.setOpen(!wasOpen);
targetBlock.setBlockData(op);
if (soundsEnabled) {
String soundKey = wasOpen ? "sounds.iron-door-close" : "sounds.iron-door-open";
playConfigSound(location, soundKey,
wasOpen ? "BLOCK_IRON_TRAPDOOR_CLOSE" : "BLOCK_IRON_TRAPDOOR_OPEN");
}
if (!wasOpen) anyIronTrapOpened = true; else anyIronTrapClosed = true;
}
continue;
}
// ── Holztür / Zauntore / Falltüren ────────────────────────────
if (isDoor(mat) || isGate(mat) || isTrapdoor(mat)) {
if (targetBlock.getBlockData() instanceof Openable) {
Openable op = (Openable) targetBlock.getBlockData();
boolean wasOpen = op.isOpen();
op.setOpen(!wasOpen);
targetBlock.setBlockData(op);
if (soundsEnabled) {
String soundKey = wasOpen ? "sounds.door-close" : "sounds.door-open";
String fallback = wasOpen ? "BLOCK_WOODEN_DOOR_CLOSE" : "BLOCK_WOODEN_DOOR_OPEN";
playConfigSound(location, soundKey, fallback);
}
if (isDoor(mat)) { if (!wasOpen) anyDoorOpened = true; else anyDoorClosed = true; }
else if (isGate(mat)) { if (!wasOpen) anyGateOpened = true; else anyGateClosed = true; }
else { if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true; }
}
}
// ── Redstone-Lampe ────────────────────────────────────────────
else if (mat == Material.REDSTONE_LAMP) {
Lightable lamp = (Lightable) targetBlock.getBlockData();
boolean wasLit = lamp.isLit();
lamp.setLit(!wasLit);
targetBlock.setBlockData(lamp);
if (soundsEnabled) {
playConfigSound(location,
wasLit ? "sounds.lamp-off" : "sounds.lamp-on",
"BLOCK_LEVER_CLICK");
}
if (!wasLit) anyLampOn = true; else anyLampOff = true;
}
// ── Notenblock ────────────────────────────────────────────────
else if (mat == Material.NOTE_BLOCK) {
String instrument = dataManager.getPlayerInstrument(playerUUID);
if (instrument == null)
instrument = configManager.getConfig().getString("default-note", "PIANO");
plugin.playDoorbellSound(location, instrument);
anyNoteBlockPlayed = true;
}
// ── Glocke ───────────────────────────────────────────────────
else if (mat == Material.BELL) {
targetBlock.getWorld().playSound(location, Sound.BLOCK_BELL_USE, 3.0f, 1.0f);
anyBellPlayed = true;
}
}
// Feedback-Nachrichten
if (anyDoorOpened) player.sendMessage(configManager.getMessage("tueren-geoeffnet"));
if (anyDoorClosed) player.sendMessage(configManager.getMessage("tueren-geschlossen"));
if (anyIronDoorOpened) player.sendMessage(configManager.getMessage("eisentueren-geoeffnet"));
if (anyIronDoorClosed) player.sendMessage(configManager.getMessage("eisentueren-geschlossen"));
if (anyIronTrapOpened) player.sendMessage(configManager.getMessage("eisenfallturen-geoeffnet"));
if (anyIronTrapClosed) player.sendMessage(configManager.getMessage("eisenfallturen-geschlossen"));
if (anyGateOpened) player.sendMessage(configManager.getMessage("gates-geoeffnet"));
if (anyGateClosed) player.sendMessage(configManager.getMessage("gates-geschlossen"));
if (anyTrapOpened) player.sendMessage(configManager.getMessage("fallturen-geoeffnet"));
if (anyTrapClosed) player.sendMessage(configManager.getMessage("fallturen-geschlossen"));
if (anyLampOn) player.sendMessage(configManager.getMessage("lampen-eingeschaltet"));
if (anyLampOff) player.sendMessage(configManager.getMessage("lampen-ausgeschaltet"));
if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest"));
if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet"));
} }
private Material getMaterialFromLocation(String locString) { /**
String[] parts = locString.split(","); * Spielt einen Sound ab dessen Name aus der config.yml gelesen wird.
if (parts.length != 4) return null; * Ist der Key nicht gesetzt oder der Sound-Name ungültig, wird der Fallback verwendet.
Location loc = new Location(plugin.getServer().getWorld(parts[0]), */
Integer.parseInt(parts[1]), private void playConfigSound(Location loc, String configKey, String fallback) {
Integer.parseInt(parts[2]), String soundName = configManager.getConfig().getString(configKey, fallback);
Integer.parseInt(parts[3])); try {
return loc.getBlock().getType(); Sound sound = Sound.valueOf(soundName.toUpperCase());
loc.getWorld().playSound(loc, sound, 1.0f, 1.0f);
} catch (IllegalArgumentException e) {
try {
Sound sound = Sound.valueOf(fallback.toUpperCase());
loc.getWorld().playSound(loc, sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) { }
}
}
// -----------------------------------------------------------------------
// Limits
// -----------------------------------------------------------------------
private boolean checkLimits(Player player, Material type, List<String> connected) {
if (type == Material.IRON_DOOR) {
if (connected.stream().filter(l -> getMaterialAt(l) == Material.IRON_DOOR).count()
>= configManager.getMaxDoors()) {
player.sendMessage(configManager.getMessage("max-tueren-erreicht")); return false;
}
} else if (type == Material.IRON_TRAPDOOR) {
if (connected.stream().filter(l -> getMaterialAt(l) == Material.IRON_TRAPDOOR).count()
>= configManager.getMaxTrapdoors()) {
player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false;
}
} else if (isDoor(type)) {
if (connected.stream().filter(l -> isDoor(getMaterialAt(l))).count()
>= configManager.getMaxDoors()) {
player.sendMessage(configManager.getMessage("max-tueren-erreicht")); return false;
}
} else if (isGate(type)) {
if (connected.stream().filter(l -> isGate(getMaterialAt(l))).count()
>= configManager.getMaxGates()) {
player.sendMessage(configManager.getMessage("max-gates-erreicht")); return false;
}
} else if (isTrapdoor(type)) {
if (connected.stream().filter(l -> isTrapdoor(getMaterialAt(l))).count()
>= configManager.getMaxTrapdoors()) {
player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false;
}
} else if (type == Material.REDSTONE_LAMP) {
if (connected.stream().filter(l -> getMaterialAt(l) == Material.REDSTONE_LAMP).count()
>= configManager.getMaxLamps()) {
player.sendMessage(configManager.getMessage("max-lampen-erreicht")); return false;
}
} else if (type == Material.NOTE_BLOCK) {
if (connected.stream().filter(l -> getMaterialAt(l) == Material.NOTE_BLOCK).count()
>= configManager.getMaxNoteBlocks()) {
player.sendMessage(configManager.getMessage("max-notenbloecke-erreicht")); return false;
}
} else if (type == Material.BELL) {
if (connected.stream().filter(l -> getMaterialAt(l) == Material.BELL).count()
>= configManager.getMaxBells()) {
player.sendMessage(configManager.getMessage("max-glocken-erreicht")); return false;
}
}
return true;
}
// -----------------------------------------------------------------------
// Hilfsmethoden
// -----------------------------------------------------------------------
/** Gibt bei zweiteiligen Türen immer den untersten Block zurück. */
private Block getBottomDoorBlock(Block block) {
Material mat = block.getType();
if (!isDoor(mat) && mat != Material.IRON_DOOR) return block;
if (block.getBlockData() instanceof Bisected) {
Bisected b = (Bisected) block.getBlockData();
if (b.getHalf() == Bisected.Half.TOP) return block.getRelative(0, -1, 0);
}
return block;
}
private String extractButtonId(ItemMeta meta) {
if (meta == null || !meta.hasLore() || meta.getLore().isEmpty()) return null;
String first = meta.getLore().get(0);
return first.startsWith("§8ID: ") ? first.replace("§8ID: ", "") : null;
} }
private void updateButtonLore(ItemStack item, String buttonId) { private void updateButtonLore(ItemStack item, String buttonId) {
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta != null) { if (meta != null) {
List<String> lore = meta.hasLore() ? meta.getLore() : new ArrayList<>(); List<String> lore = new ArrayList<>();
if (!lore.contains(buttonId)) { lore.add("§8ID: " + buttonId);
lore.add(buttonId); lore.add("§7Ein universeller Controller für");
meta.setLore(lore); meta.setLore(lore);
item.setItemMeta(meta); item.setItemMeta(meta);
}
} }
} }
private boolean isButton(Material m) { return m.name().endsWith("_BUTTON"); }
private boolean isDoor(Material m) { return m.name().endsWith("_DOOR") && m != Material.IRON_DOOR; }
private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); }
private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR") && m != Material.IRON_TRAPDOOR; }
private boolean isInteractableTarget(Material m) {
return isDoor(m) || isGate(m) || isTrapdoor(m)
|| m == Material.IRON_DOOR || m == Material.IRON_TRAPDOOR
|| m == Material.REDSTONE_LAMP || m == Material.NOTE_BLOCK || m == Material.BELL;
}
private Material getMaterialAt(String locString) {
Location l = parseLocation(locString);
return l != null ? l.getBlock().getType() : Material.AIR;
}
private Location parseLocation(String s) {
String[] p = s.split(",");
if (p.length != 4) return null;
try {
return new Location(Bukkit.getWorld(p[0]),
Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3]));
} catch (Exception e) { return null; }
}
} }

View File

@@ -0,0 +1,53 @@
package viper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ButtonTabCompleter implements TabCompleter {
private final List<String> commands = Arrays.asList(
"info", "reload", "note", "list", "rename", "schedule",
"trust", "untrust", "public", "private"
);
private final List<String> instruments = Arrays.asList(
"PIANO", "BASS_DRUM", "SNARE_DRUM", "STICKS", "BASS_GUITAR",
"FLUTE", "BELL", "CHIME", "GUITAR", "XYLOPHONE",
"IRON_XYLOPHONE", "COW_BELL", "DIDGERIDOO", "BIT", "BANJO", "PLING"
);
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (!(sender instanceof Player)) return Collections.emptyList();
List<String> completions = new ArrayList<>();
if (args.length == 1) {
StringUtil.copyPartialMatches(args[0], commands, completions);
} else if (args.length == 2) {
switch (args[0].toLowerCase()) {
case "note":
StringUtil.copyPartialMatches(args[1], instruments, completions);
break;
case "trust":
case "untrust":
return null; // Bukkit schlägt automatisch Online-Spieler vor
case "rename":
completions.add("<Name>");
break;
}
}
Collections.sort(completions);
return completions;
}
}

View File

@@ -21,143 +21,176 @@ public class ConfigManager {
private void loadConfig() { private void loadConfig() {
configFile = new File(plugin.getDataFolder(), "config.yml"); configFile = new File(plugin.getDataFolder(), "config.yml");
if (!configFile.exists()) { if (!configFile.exists()) plugin.saveResource("config.yml", false);
plugin.saveResource("config.yml", false);
}
config = YamlConfiguration.loadConfiguration(configFile); config = YamlConfiguration.loadConfiguration(configFile);
mergeDefaults(config, "config.yml", configFile); mergeDefaults(config, "config.yml", configFile);
setConfigDefaults(); setConfigDefaults();
} }
private void loadLang() { private void loadLang() {
langFile = new File(plugin.getDataFolder(), "lang.yml"); langFile = new File(plugin.getDataFolder(), "lang.yml");
if (!langFile.exists()) { if (!langFile.exists()) plugin.saveResource("lang.yml", false);
plugin.saveResource("lang.yml", false);
}
lang = YamlConfiguration.loadConfiguration(langFile); lang = YamlConfiguration.loadConfiguration(langFile);
mergeDefaults(lang, "lang.yml", langFile); mergeDefaults(lang, "lang.yml", langFile);
setLangDefaults(); setLangDefaults();
} }
/**
* FIX: Null-Check VOR dem try-Block verhindert NPE wenn Ressource nicht im JAR.
*/
private void mergeDefaults(FileConfiguration file, String resourceName, File targetFile) { private void mergeDefaults(FileConfiguration file, String resourceName, File targetFile) {
try (InputStream is = plugin.getResource(resourceName); InputStream is = plugin.getResource(resourceName);
InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { if (is == null) {
if (is == null) { plugin.getLogger().warning(resourceName + " nicht im JAR gefunden.");
plugin.getLogger().warning(resourceName + " nicht im Plugin-Jar gefunden, Merge übersprungen."); return;
return; }
} try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
FileConfiguration defaults = YamlConfiguration.loadConfiguration(reader); FileConfiguration defaults = YamlConfiguration.loadConfiguration(reader);
boolean changed = false; boolean changed = false;
for (String key : defaults.getKeys(true)) { for (String key : defaults.getKeys(true)) {
if (!file.contains(key)) { if (!file.contains(key)) {
file.set(key, defaults.get(key)); file.set(key, defaults.get(key));
changed = true; changed = true;
plugin.getLogger().info("[ConfigManager] Neuer Key in " + resourceName + " hinzugefügt: " + key);
} }
} }
if (changed) { if (changed) {
file.save(targetFile); file.save(targetFile);
plugin.getLogger().info(resourceName + " wurde mit neuen Standardwerten ergänzt.");
} }
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().severe("Fehler beim Mergen von " + resourceName + ": " + e.getMessage()); plugin.getLogger().severe("Merge-Fehler " + resourceName + ": " + e.getMessage());
} }
} }
private void setConfigDefaults() { private void setConfigDefaults() {
if (!config.contains("max-doors")) config.set("max-doors", 20); def(config, "max-doors", 20);
if (!config.contains("max-lamps")) config.set("max-lamps", 50); def(config, "max-lamps", 50);
if (!config.contains("max-noteblocks")) config.set("max-noteblocks", 10); def(config, "max-noteblocks", 10);
if (!config.contains("max-gates")) config.set("max-gates", 20); def(config, "max-gates", 20);
if (!config.contains("max-trapdoors")) config.set("max-trapdoors", 20); def(config, "max-trapdoors", 20);
if (!config.contains("max-bells")) config.set("max-bells", 5); def(config, "max-bells", 5);
if (!config.contains("default-note")) config.set("default-note", "PIANO"); def(config, "default-note", "PIANO");
if (!config.contains("double-note-enabled")) config.set("double-note-enabled", true); def(config, "double-note-enabled", true);
if (!config.contains("double-note-delay-ms")) config.set("double-note-delay-ms", 1000); def(config, "double-note-delay-ms", 1000);
if (!config.contains("motion-detection-radius")) config.set("motion-detection-radius", 5.0); def(config, "motion-detection-radius", 5.0);
if (!config.contains("motion-close-delay-ms")) config.set("motion-close-delay-ms", 5000); def(config, "motion-close-delay-ms", 5000);
def(config, "motion-trigger-cooldown-ms", 2000);
// Optionales MySQL-Backend
def(config, "mysql.enabled", false);
def(config, "mysql.host", "127.0.0.1");
def(config, "mysql.port", 3306);
def(config, "mysql.database", "buttoncontrol");
def(config, "mysql.user", "root");
def(config, "mysql.password", "");
// Controller-Namensanzeige beim Anschauen
def(config, "controller-name-display.enabled", true);
def(config, "controller-name-display.max-look-distance", 8);
def(config, "controller-name-display.format", "§6Controller: §f%s");
// Sounds (NEU)
def(config, "sounds.enabled", true);
def(config, "sounds.door-open", "BLOCK_WOODEN_DOOR_OPEN");
def(config, "sounds.door-close", "BLOCK_WOODEN_DOOR_CLOSE");
def(config, "sounds.iron-door-open", "BLOCK_IRON_DOOR_OPEN");
def(config, "sounds.iron-door-close", "BLOCK_IRON_DOOR_CLOSE");
def(config, "sounds.lamp-on", "BLOCK_LEVER_CLICK");
def(config, "sounds.lamp-off", "BLOCK_LEVER_CLICK");
saveConfig(); saveConfig();
} }
private void setLangDefaults() { private void setLangDefaults() {
if (!lang.contains("tueren-geoeffnet")) lang.set("tueren-geoeffnet", "§aTüren wurden geöffnet."); // Türen (Holz)
if (!lang.contains("tueren-geschlossen")) lang.set("tueren-geschlossen", "§cTüren wurden geschlossen."); def(lang, "tueren-geoeffnet", "§aTüren wurden geöffnet.");
if (!lang.contains("max-tueren-erreicht")) lang.set("max-tueren-erreicht", "§cMaximale Anzahl an Türen erreicht."); def(lang, "tueren-geschlossen", "§cTüren wurden geschlossen.");
def(lang, "max-tueren-erreicht", "§cMaximale Anzahl an Türen erreicht.");
if (!lang.contains("lampen-eingeschaltet")) lang.set("lampen-eingeschaltet", "§aLampen wurden eingeschaltet."); // Eisentüren (NEU)
if (!lang.contains("lampen-ausgeschaltet")) lang.set("lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet."); def(lang, "eisentueren-geoeffnet", "§aEisentüren wurden geöffnet.");
if (!lang.contains("max-lampen-erreicht")) lang.set("max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht."); def(lang, "eisentueren-geschlossen", "§cEisentüren wurden geschlossen.");
def(lang, "eisenfallturen-geoeffnet", "§aEisen-Falltüren wurden geöffnet.");
if (!lang.contains("notenblock-ausgeloest")) lang.set("notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst."); def(lang, "eisenfallturen-geschlossen", "§cEisen-Falltüren wurden geschlossen.");
if (!lang.contains("max-notenbloecke-erreicht")) lang.set("max-notenbloecke-erreicht", "§cMaximale Anzahl an Notenblöcken erreicht."); // Zauntore
def(lang, "gates-geoeffnet", "§aZauntore wurden geöffnet.");
if (!lang.contains("gates-geoeffnet")) lang.set("gates-geoeffnet", "§aZauntore wurden geöffnet."); def(lang, "gates-geschlossen", "§cZauntore wurden geschlossen.");
if (!lang.contains("gates-geschlossen")) lang.set("gates-geschlossen", "§cZauntore wurden geschlossen."); def(lang, "max-gates-erreicht", "§cMaximale Anzahl an Zauntoren erreicht.");
if (!lang.contains("max-gates-erreicht")) lang.set("max-gates-erreicht", "§cMaximale Anzahl an Zauntoren erreicht."); // Falltüren
def(lang, "fallturen-geoeffnet", "§aFalltüren wurden geöffnet.");
if (!lang.contains("fallturen-geoeffnet")) lang.set("fallturen-geoeffnet", "§aFalltüren wurden geöffnet."); def(lang, "fallturen-geschlossen", "§cFalltüren wurden geschlossen.");
if (!lang.contains("fallturen-geschlossen")) lang.set("fallturen-geschlossen", "§cFalltüren wurden geschlossen."); def(lang, "max-fallturen-erreicht", "§cMaximale Anzahl an Falltüren erreicht.");
if (!lang.contains("max-fallturen-erreicht")) lang.set("max-fallturen-erreicht", "§cMaximale Anzahl an Falltüren erreicht."); // Lampen
def(lang, "lampen-eingeschaltet", "§aLampen wurden eingeschaltet.");
if (!lang.contains("glocke-gelaeutet")) lang.set("glocke-gelaeutet", "§eGlocke wurde geläutet."); def(lang, "lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet.");
if (!lang.contains("max-glocken-erreicht")) lang.set("max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht."); def(lang, "max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht.");
// Glocken
if (!lang.contains("bloecke-umgeschaltet")) lang.set("bloecke-umgeschaltet", "§eBlöcke wurden umgeschaltet."); def(lang, "glocke-gelaeutet", "§aGlocke wurde geläutet.");
if (!lang.contains("keine-bloecke-verbunden")) lang.set("keine-bloecke-verbunden", "§cKeine Blöcke sind verbunden."); def(lang, "max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht.");
if (!lang.contains("block-verbunden")) lang.set("block-verbunden", "§aBlock verbunden."); // Notenblöcke
if (!lang.contains("block-bereits-verbunden")) lang.set("block-bereits-verbunden", "§cBlock ist bereits verbunden."); def(lang, "notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst.");
if (!lang.contains("controller-platziert")) lang.set("controller-platziert", "§aController platziert."); def(lang, "instrument-gesetzt", "§aDein Instrument wurde auf %s gesetzt.");
if (!lang.contains("controller-entfernt")) lang.set("controller-entfernt", "§cController entfernt."); def(lang, "ungueltiges-instrument", "§cUngültiges Instrument! /bc note <Instrument>");
if (!lang.contains("instrument-gesetzt")) lang.set("instrument-gesetzt", "§aDein Notenblock-Instrument wurde auf %s gesetzt."); def(lang, "max-notenbloecke-erreicht", "§cMaximale Anzahl an Notenblöcken erreicht.");
if (!lang.contains("ungueltiges-instrument")) lang.set("ungueltiges-instrument", "§cUngültiges Instrument! Verwende: /bc note <Instrument>"); // Kolben (vorbereitet)
if (!lang.contains("konfiguration-reloaded")) lang.set("konfiguration-reloaded", "§aKonfiguration und Daten erfolgreich neu geladen!"); def(lang, "kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren.");
if (!lang.contains("keine-berechtigung")) lang.set("keine-berechtigung", "§cDu hast keine Berechtigung für diesen Befehl!"); def(lang, "kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen.");
if (!lang.contains("kolben-ausgefahren")) lang.set("kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren."); def(lang, "max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht.");
if (!lang.contains("kolben-eingefahren")) lang.set("kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen."); // Controller
if (!lang.contains("max-kolben-erreicht")) lang.set("max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."); def(lang, "block-verbunden", "§aBlock verbunden.");
def(lang, "block-bereits-verbunden", "§cBlock ist bereits verbunden.");
def(lang, "block-verbindung-entfernt", "§7Verbindung zu abgebautem Block automatisch entfernt.");
def(lang, "keine-bloecke-verbunden", "§cKeine Blöcke sind verbunden.");
def(lang, "bloecke-umgeschaltet", "§eBlöcke wurden umgeschaltet.");
def(lang, "controller-platziert", "§aController platziert.");
def(lang, "controller-entfernt", "§cController entfernt.");
def(lang, "controller-umbenannt", "§aController umbenannt zu: §f%s"); // NEU
// Trust & Berechtigungen
def(lang, "keine-berechtigung", "§cDu hast keine Berechtigung!");
def(lang, "keine-berechtigung-controller", "§cDu darfst diesen Controller nicht benutzen!");
def(lang, "nur-besitzer-abbauen", "§cNur der Besitzer kann diesen Controller verwalten!");
def(lang, "spieler-nicht-gefunden", "§cSpieler nicht gefunden.");
def(lang, "status-geandert", "§6[BC] §7Controller ist nun %s§7.");
def(lang, "trust-hinzugefuegt", "§a%s darf diesen Controller nun benutzen.");
def(lang, "trust-entfernt", "§c%s wurde das Vertrauen entzogen.");
def(lang, "kein-controller-im-blick", "§cBitte sieh einen Controller direkt an!");
// System
// FIX: Korrekte Key-Bezeichnung (war fälschlich "konfiguration-reloaded")
def(lang, "konfiguration-neugeladen", "§aKonfiguration erfolgreich neu geladen!");
saveLang(); saveLang();
} }
private void def(FileConfiguration cfg, String key, Object value) {
if (!cfg.contains(key)) cfg.set(key, value);
}
public void reloadConfig() { public void reloadConfig() {
config = YamlConfiguration.loadConfiguration(configFile); config = YamlConfiguration.loadConfiguration(configFile);
lang = YamlConfiguration.loadConfiguration(langFile); lang = YamlConfiguration.loadConfiguration(langFile);
mergeDefaults(config, "config.yml", configFile); mergeDefaults(config, "config.yml", configFile);
mergeDefaults(lang, "lang.yml", langFile); mergeDefaults(lang, "lang.yml", langFile);
setConfigDefaults(); setConfigDefaults();
setLangDefaults(); setLangDefaults();
} }
public FileConfiguration getConfig() { public FileConfiguration getConfig() { return config; }
return config;
}
public int getMaxDoors() { return config.getInt("max-doors", 20); } public int getMaxDoors() { return config.getInt("max-doors", 20); }
public int getMaxLamps() { return config.getInt("max-lamps", 50); } public int getMaxLamps() { return config.getInt("max-lamps", 50); }
public int getMaxNoteBlocks() { return config.getInt("max-noteblocks", 10); } public int getMaxNoteBlocks() { return config.getInt("max-noteblocks", 10); }
public int getMaxGates() { return config.getInt("max-gates", getMaxDoors()); } public int getMaxGates() { return config.getInt("max-gates", 20); }
public int getMaxTrapdoors() { return config.getInt("max-trapdoors", getMaxDoors()); } public int getMaxTrapdoors() { return config.getInt("max-trapdoors", 20); }
public int getMaxBells() { return config.getInt("max-bells", 5); } public int getMaxBells() { return config.getInt("max-bells", 5); }
public String getMessage(String key) { public String getMessage(String key) {
return lang.getString(key, "§cNachricht nicht gefunden: " + key); return lang.getString(key, "§cNachricht fehlt: " + key);
} }
public void saveConfig() { public void saveConfig() {
try { try { config.save(configFile); }
config.save(configFile); catch (IOException e) { plugin.getLogger().severe("config.yml Fehler: " + e.getMessage()); }
} catch (IOException e) {
plugin.getLogger().severe("Konnte config.yml nicht speichern: " + e.getMessage());
}
} }
public void saveLang() { public void saveLang() {
try { try { lang.save(langFile); }
lang.save(langFile); catch (IOException e) { plugin.getLogger().severe("lang.yml Fehler: " + e.getMessage()); }
} catch (IOException e) {
plugin.getLogger().severe("Konnte lang.yml nicht speichern: " + e.getMessage());
}
} }
} }

View File

@@ -4,137 +4,374 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File; import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class DataManager { public class DataManager {
private final ButtonControl plugin; private final ButtonControl plugin;
private FileConfiguration data; private FileConfiguration data;
private File dataFile; private File dataFile;
private MySQLStorage mySQLStorage;
public DataManager(ButtonControl plugin) { public DataManager(ButtonControl plugin) {
this.plugin = plugin; this.plugin = plugin;
loadData(); loadData();
initializeStorage();
}
private void initializeStorage() {
mySQLStorage = new MySQLStorage(plugin);
if (!mySQLStorage.initialize()) {
mySQLStorage = null;
}
} }
private void loadData() { private void loadData() {
dataFile = new File(plugin.getDataFolder(), "data.yml"); dataFile = new File(plugin.getDataFolder(), "data.yml");
if (!dataFile.exists()) { if (!dataFile.exists()) plugin.saveResource("data.yml", false);
plugin.saveResource("data.yml", false);
}
data = YamlConfiguration.loadConfiguration(dataFile); data = YamlConfiguration.loadConfiguration(dataFile);
} }
public void reloadData() { public void reloadData() {
data = YamlConfiguration.loadConfiguration(dataFile); data = YamlConfiguration.loadConfiguration(dataFile);
if (mySQLStorage != null) {
mySQLStorage.close();
}
initializeStorage();
} }
public List<String> getConnectedBlocks(String playerUUID, String buttonId) { public void shutdown() {
return data.getStringList("players." + playerUUID + ".buttons." + buttonId); if (mySQLStorage != null) {
mySQLStorage.close();
}
} }
// -----------------------------------------------------------------------
// Zugriff & Berechtigungen
// -----------------------------------------------------------------------
public boolean canAccess(String buttonId, UUID playerUUID) {
if (mySQLStorage != null) return mySQLStorage.canAccess(buttonId, playerUUID);
if (isPublic(buttonId)) return true;
if (isOwner(buttonId, playerUUID)) return true;
return data.getStringList("trust." + buttonId).contains(playerUUID.toString());
}
public boolean isOwner(String buttonId, UUID playerUUID) {
if (mySQLStorage != null) return mySQLStorage.isOwner(buttonId, playerUUID);
return data.contains("players." + playerUUID + ".buttons." + buttonId)
|| data.contains("players." + playerUUID + ".placed-controllers");
// Zweite Bedingung: prüft ob irgendein placed-controller dieser UUID die buttonId enthält
}
// -----------------------------------------------------------------------
// Controller-Verwaltung
// -----------------------------------------------------------------------
public String getButtonIdForLocation(String location) {
if (mySQLStorage != null) return mySQLStorage.getButtonIdForLocation(location);
return getButtonIdForPlacedController(location);
}
public void registerController(String location, UUID ownerUUID, String buttonId) {
if (mySQLStorage != null) {
mySQLStorage.registerController(location, ownerUUID, buttonId);
return;
}
data.set("players." + ownerUUID + ".placed-controllers." + location, buttonId);
// Leere buttons-Liste anlegen damit isOwner() sofort greift
if (!data.contains("players." + ownerUUID + ".buttons." + buttonId)) {
data.set("players." + ownerUUID + ".buttons." + buttonId, new ArrayList<>());
}
saveData();
}
public void removeController(String location) {
if (mySQLStorage != null) {
mySQLStorage.removeController(location);
return;
}
// buttonId vor dem Löschen des Location-Eintrags ermitteln
String buttonId = getButtonIdForPlacedController(location);
String ownerUUID = null;
if (data.getConfigurationSection("players") != null) {
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
String path = "players." + uuid + ".placed-controllers." + location;
if (data.contains(path)) {
data.set(path, null);
ownerUUID = uuid;
}
}
}
// Alle zugehörigen Daten (Name, Status, Trust, Zeitplan, Verbindungen) bereinigen
if (buttonId != null) {
data.set("names." + buttonId, null);
data.set("public-status." + buttonId, null);
data.set("trust." + buttonId, null);
data.set("schedules." + buttonId, null);
if (ownerUUID != null) {
data.set("players." + ownerUUID + ".buttons." + buttonId, null);
}
}
removeMotionSensorSettings(location);
saveData();
}
public String getButtonIdForPlacedController(String location) {
if (mySQLStorage != null) return mySQLStorage.getButtonIdForPlacedController(location);
if (data.getConfigurationSection("players") == null) return null;
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
String val = data.getString("players." + uuid + ".placed-controllers." + location);
if (val != null) return val;
}
return null;
}
public List<String> getAllPlacedControllers() {
if (mySQLStorage != null) return mySQLStorage.getAllPlacedControllers();
List<String> result = new ArrayList<>();
if (data.getConfigurationSection("players") == null) return result;
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
var sec = data.getConfigurationSection("players." + uuid + ".placed-controllers");
if (sec != null) result.addAll(sec.getKeys(false));
}
return result;
}
// -----------------------------------------------------------------------
// Verbundene Blöcke
// -----------------------------------------------------------------------
public void setConnectedBlocks(String playerUUID, String buttonId, List<String> blocks) { public void setConnectedBlocks(String playerUUID, String buttonId, List<String> blocks) {
if (mySQLStorage != null) {
mySQLStorage.setConnectedBlocks(playerUUID, buttonId, blocks);
return;
}
data.set("players." + playerUUID + ".buttons." + buttonId, blocks); data.set("players." + playerUUID + ".buttons." + buttonId, blocks);
saveData(); saveData();
} }
public void addPlacedController(String playerUUID, String location, String buttonId) {
data.set("players." + playerUUID + ".placed-controllers." + location, buttonId);
saveData();
}
public String getButtonIdForPlacedController(String playerUUID, String location) {
return data.getString("players." + playerUUID + ".placed-controllers." + location);
}
public void removePlacedController(String playerUUID, String location) {
data.set("players." + playerUUID + ".placed-controllers." + location, null);
saveData();
}
public List<String> getAllPlacedControllers(String playerUUID) {
if (data.getConfigurationSection("players." + playerUUID + ".placed-controllers") == null) {
return new ArrayList<>();
}
Set<String> keys = data.getConfigurationSection("players." + playerUUID + ".placed-controllers").getKeys(false);
return new ArrayList<>(keys);
}
public List<String> getAllPlacedControllers() {
List<String> allControllers = new ArrayList<>();
if (data.getConfigurationSection("players") == null) {
return allControllers;
}
Set<String> players = data.getConfigurationSection("players").getKeys(false);
for (String playerUUID : players) {
allControllers.addAll(getAllPlacedControllers(playerUUID));
}
return allControllers;
}
public String getButtonIdForPlacedController(String location) {
if (data.getConfigurationSection("players") == null) return null;
Set<String> players = data.getConfigurationSection("players").getKeys(false);
for (String playerUUID : players) {
String buttonId = getButtonIdForPlacedController(playerUUID, location);
if (buttonId != null) return buttonId;
}
return null;
}
public List<String> getConnectedBlocks(String buttonId) { public List<String> getConnectedBlocks(String buttonId) {
if (data.getConfigurationSection("players") == null) return null; if (mySQLStorage != null) return mySQLStorage.getConnectedBlocks(buttonId);
Set<String> players = data.getConfigurationSection("players").getKeys(false); if (data.getConfigurationSection("players") == null) return new ArrayList<>();
for (String playerUUID : players) { for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
List<String> connected = getConnectedBlocks(playerUUID, buttonId); String path = "players." + uuid + ".buttons." + buttonId;
if (connected != null && !connected.isEmpty()) { if (data.contains(path)) return data.getStringList(path);
return connected; }
return new ArrayList<>();
}
/**
* Entfernt eine Block-Location aus ALLEN Verbindungslisten aller Controller.
* Wird aufgerufen wenn ein verbundener Block abgebaut wird.
*/
public boolean removeFromAllConnectedBlocks(String locStr) {
if (mySQLStorage != null) return mySQLStorage.removeFromAllConnectedBlocks(locStr);
if (data.getConfigurationSection("players") == null) return false;
boolean changed = false;
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
var buttonsSection = data.getConfigurationSection("players." + uuid + ".buttons");
if (buttonsSection == null) continue;
for (String buttonId : buttonsSection.getKeys(false)) {
String path = "players." + uuid + ".buttons." + buttonId;
List<String> blocks = data.getStringList(path);
if (blocks.remove(locStr)) {
data.set(path, blocks);
changed = true;
}
} }
} }
return null; if (changed) saveData();
return changed;
} }
// -----------------------------------------------------------------------
// Controller-Name (NEU)
// -----------------------------------------------------------------------
public void setControllerName(String buttonId, String name) {
if (mySQLStorage != null) {
mySQLStorage.setControllerName(buttonId, name);
return;
}
data.set("names." + buttonId, name);
saveData();
}
public String getControllerName(String buttonId) {
if (mySQLStorage != null) return mySQLStorage.getControllerName(buttonId);
return data.getString("names." + buttonId);
}
// -----------------------------------------------------------------------
// Zeitplan (NEU)
// -----------------------------------------------------------------------
public void setScheduleOpenTime(String buttonId, long ticks) {
if (mySQLStorage != null) {
mySQLStorage.setScheduleOpenTime(buttonId, ticks);
return;
}
data.set("schedules." + buttonId + ".open-time", ticks);
saveData();
}
public long getScheduleOpenTime(String buttonId) {
if (mySQLStorage != null) return mySQLStorage.getScheduleOpenTime(buttonId);
return data.getLong("schedules." + buttonId + ".open-time", -1);
}
public void setScheduleCloseTime(String buttonId, long ticks) {
if (mySQLStorage != null) {
mySQLStorage.setScheduleCloseTime(buttonId, ticks);
return;
}
data.set("schedules." + buttonId + ".close-time", ticks);
saveData();
}
public long getScheduleCloseTime(String buttonId) {
if (mySQLStorage != null) return mySQLStorage.getScheduleCloseTime(buttonId);
return data.getLong("schedules." + buttonId + ".close-time", -1);
}
/** Entfernt den kompletten Zeitplan für einen Controller. */
public void clearSchedule(String buttonId) {
if (mySQLStorage != null) {
mySQLStorage.clearSchedule(buttonId);
return;
}
data.set("schedules." + buttonId, null);
saveData();
}
// -----------------------------------------------------------------------
// Trust & Public/Private
// -----------------------------------------------------------------------
public void addTrustedPlayer(String buttonId, UUID targetUUID) {
if (mySQLStorage != null) {
mySQLStorage.addTrustedPlayer(buttonId, targetUUID);
return;
}
List<String> trusted = data.getStringList("trust." + buttonId);
if (!trusted.contains(targetUUID.toString())) {
trusted.add(targetUUID.toString());
data.set("trust." + buttonId, trusted);
saveData();
}
}
public void removeTrustedPlayer(String buttonId, UUID targetUUID) {
if (mySQLStorage != null) {
mySQLStorage.removeTrustedPlayer(buttonId, targetUUID);
return;
}
List<String> trusted = data.getStringList("trust." + buttonId);
trusted.remove(targetUUID.toString());
data.set("trust." + buttonId, trusted);
saveData();
}
public void setPublic(String buttonId, boolean isPublic) {
if (mySQLStorage != null) {
mySQLStorage.setPublic(buttonId, isPublic);
return;
}
data.set("public-status." + buttonId, isPublic);
saveData();
}
public boolean isPublic(String buttonId) {
if (mySQLStorage != null) return mySQLStorage.isPublic(buttonId);
return data.getBoolean("public-status." + buttonId, false);
}
// -----------------------------------------------------------------------
// Instrumente
// -----------------------------------------------------------------------
public void setPlayerInstrument(UUID playerUUID, String instrument) { public void setPlayerInstrument(UUID playerUUID, String instrument) {
data.set("players." + playerUUID.toString() + ".instrument", instrument); if (mySQLStorage != null) {
mySQLStorage.setPlayerInstrument(playerUUID, instrument);
return;
}
data.set("players." + playerUUID + ".instrument", instrument);
saveData(); saveData();
} }
public String getPlayerInstrument(UUID playerUUID) { public String getPlayerInstrument(UUID playerUUID) {
return data.getString("players." + playerUUID.toString() + ".instrument"); if (mySQLStorage != null) return mySQLStorage.getPlayerInstrument(playerUUID);
return data.getString("players." + playerUUID + ".instrument");
} }
// Bewegungsmelder-Einstellungen // -----------------------------------------------------------------------
// Motion-Sensor-Einstellungen
// -----------------------------------------------------------------------
public void setMotionSensorRadius(String location, double radius) { public void setMotionSensorRadius(String location, double radius) {
if (mySQLStorage != null) {
mySQLStorage.setMotionSensorRadius(location, radius);
return;
}
data.set("motion-sensors." + location + ".radius", radius); data.set("motion-sensors." + location + ".radius", radius);
saveData(); saveData();
} }
public double getMotionSensorRadius(String location) { public double getMotionSensorRadius(String location) {
if (mySQLStorage != null) return mySQLStorage.getMotionSensorRadius(location);
return data.getDouble("motion-sensors." + location + ".radius", -1); return data.getDouble("motion-sensors." + location + ".radius", -1);
} }
public void setMotionSensorDelay(String location, long delay) { public void setMotionSensorDelay(String location, long delay) {
if (mySQLStorage != null) {
mySQLStorage.setMotionSensorDelay(location, delay);
return;
}
data.set("motion-sensors." + location + ".delay", delay); data.set("motion-sensors." + location + ".delay", delay);
saveData(); saveData();
} }
public long getMotionSensorDelay(String location) { public long getMotionSensorDelay(String location) {
if (mySQLStorage != null) return mySQLStorage.getMotionSensorDelay(location);
return data.getLong("motion-sensors." + location + ".delay", -1); return data.getLong("motion-sensors." + location + ".delay", -1);
} }
public void removeMotionSensorSettings(String location) { public void removeMotionSensorSettings(String location) {
if (mySQLStorage != null) {
mySQLStorage.removeMotionSensorSettings(location);
return;
}
data.set("motion-sensors." + location, null); data.set("motion-sensors." + location, null);
saveData(); saveData();
} }
// -----------------------------------------------------------------------
// Speichern asynchron
// -----------------------------------------------------------------------
/**
* Serialisiert die Daten synchron (thread-safe), schreibt dann asynchron auf Disk.
* Verhindert I/O-Lags auf dem Main-Thread.
*/
public void saveData() { public void saveData() {
if (mySQLStorage != null) return;
final String serialized;
try { try {
data.save(dataFile); serialized = data.saveToString();
} catch (IOException e) { } catch (Exception e) {
plugin.getLogger().severe("Konnte data.yml nicht speichern: " + e.getMessage()); plugin.getLogger().severe("Serialisierungsfehler data.yml: " + e.getMessage());
return;
} }
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
try (FileWriter fw = new FileWriter(dataFile, false)) {
fw.write(serialized);
} catch (IOException e) {
plugin.getLogger().severe("Konnte data.yml nicht speichern: " + e.getMessage());
}
});
} }
} }

View File

@@ -1,128 +1,153 @@
package viper; package viper;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.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.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays;
import java.util.Arrays;
public class MotionSensorGUI implements Listener {
private final ButtonControl plugin; public class MotionSensorGUI implements Listener {
private final DataManager dataManager; private final ButtonControl plugin;
private final ConfigManager configManager; private final DataManager dataManager;
private final String blockLocation; private final ConfigManager configManager;
private final String buttonId; private final String blockLocation;
private final Player player; private final String buttonId;
private final Player player;
public MotionSensorGUI(ButtonControl plugin, Player player, String blockLocation, String buttonId) { private final Inventory inv;
this.plugin = plugin;
this.dataManager = plugin.getDataManager(); public MotionSensorGUI(ButtonControl plugin, Player player, String blockLocation, String buttonId) {
this.configManager = plugin.getConfigManager(); this.plugin = plugin;
this.blockLocation = blockLocation; this.dataManager = plugin.getDataManager();
this.buttonId = buttonId; this.configManager = plugin.getConfigManager();
this.player = player; this.blockLocation = blockLocation;
plugin.getServer().getPluginManager().registerEvents(this, plugin); this.buttonId = buttonId;
} this.player = player;
this.inv = Bukkit.createInventory(player, 27, "Bewegungsmelder Einstellungen");
public void open() { plugin.getServer().getPluginManager().registerEvents(this, plugin);
Inventory inv = Bukkit.createInventory(player, 27, "Bewegungsmelder Einstellungen"); }
// Aktuelle Werte aus DataManager holen oder Standardwerte aus Config public void open() {
double radius = dataManager.getMotionSensorRadius(blockLocation); // Aktuelle Werte aus DataManager holen oder Standardwerte aus Config
if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0); double radius = dataManager.getMotionSensorRadius(blockLocation);
long delay = dataManager.getMotionSensorDelay(blockLocation); if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0);
if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L); long delay = dataManager.getMotionSensorDelay(blockLocation);
if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L);
// Items für die GUI
ItemStack radiusItem = new ItemStack(Material.COMPASS); // Füllitems für leere Slots (graue Glasscheiben)
ItemMeta radiusMeta = radiusItem.getItemMeta(); ItemStack filler = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
radiusMeta.setDisplayName(ChatColor.GREEN + "Erkennungsradius: " + radius + " Blöcke"); ItemMeta fillerMeta = filler.getItemMeta();
radiusMeta.setLore(Arrays.asList( fillerMeta.setDisplayName(ChatColor.RESET + "");
ChatColor.GRAY + "Linksklick: +0.5 Blöcke", filler.setItemMeta(fillerMeta);
ChatColor.GRAY + "Rechtsklick: -0.5 Blöcke" for (int i = 0; i < 27; i++) {
)); inv.setItem(i, filler);
radiusItem.setItemMeta(radiusMeta); }
ItemStack delayItem = new ItemStack(Material.CLOCK); // Items für die GUI
ItemMeta delayMeta = delayItem.getItemMeta(); ItemStack radiusItem = new ItemStack(Material.COMPASS);
delayMeta.setDisplayName(ChatColor.GREEN + "Schließverzögerung: " + (delay / 1000.0) + " Sekunden"); ItemMeta radiusMeta = radiusItem.getItemMeta();
delayMeta.setLore(Arrays.asList( radiusMeta.setDisplayName(ChatColor.GREEN + "Erkennungsradius: " + radius + " Blöcke");
ChatColor.GRAY + "Linksklick: +1 Sekunde", radiusMeta.setLore(Arrays.asList(
ChatColor.GRAY + "Rechtsklick: -1 Sekunde" ChatColor.GRAY + "Linksklick: +0.5 Blöcke",
)); ChatColor.GRAY + "Rechtsklick: -0.5 Blöcke"
delayItem.setItemMeta(delayMeta); ));
radiusItem.setItemMeta(radiusMeta);
ItemStack saveItem = new ItemStack(Material.EMERALD);
ItemMeta saveMeta = saveItem.getItemMeta(); ItemStack delayItem = new ItemStack(Material.CLOCK);
saveMeta.setDisplayName(ChatColor.GREEN + "Speichern und Schließen"); ItemMeta delayMeta = delayItem.getItemMeta();
saveItem.setItemMeta(saveMeta); delayMeta.setDisplayName(ChatColor.GREEN + "Schließverzögerung: " + (delay / 1000.0) + " Sekunden");
delayMeta.setLore(Arrays.asList(
// Items in die GUI setzen ChatColor.GRAY + "Linksklick: +1 Sekunde",
inv.setItem(11, radiusItem); ChatColor.GRAY + "Rechtsklick: -1 Sekunde"
inv.setItem(15, delayItem); ));
inv.setItem(22, saveItem); delayItem.setItemMeta(delayMeta);
player.openInventory(inv); ItemStack saveItem = new ItemStack(Material.EMERALD);
} ItemMeta saveMeta = saveItem.getItemMeta();
saveMeta.setDisplayName(ChatColor.GREEN + "Speichern und Schließen");
@EventHandler saveItem.setItemMeta(saveMeta);
public void onInventoryClick(InventoryClickEvent event) {
if (!event.getInventory().getHolder().equals(player)) return; // Items in die GUI setzen
if (event.getCurrentItem() == null) return; inv.setItem(11, radiusItem);
event.setCancelled(true); inv.setItem(15, delayItem);
inv.setItem(22, saveItem);
ItemStack clicked = event.getCurrentItem();
double radius = dataManager.getMotionSensorRadius(blockLocation); player.openInventory(inv);
if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0); }
long delay = dataManager.getMotionSensorDelay(blockLocation);
if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L); @EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (clicked.getType() == Material.COMPASS) { if (!event.getInventory().equals(inv) || !event.getWhoClicked().equals(player)) return;
if (event.isLeftClick()) { if (event.getCurrentItem() == null) return;
radius = Math.min(radius + 0.5, 20.0); // Max. Radius: 20 Blöcke
} else if (event.isRightClick()) { event.setCancelled(true); // Alle Klicks standardmäßig abbrechen
radius = Math.max(radius - 0.5, 0.5); // Min. Radius: 0.5 Blöcke
} int slot = event.getRawSlot();
dataManager.setMotionSensorRadius(blockLocation, radius); ItemStack clicked = event.getCurrentItem();
ItemMeta meta = clicked.getItemMeta();
meta.setDisplayName(ChatColor.GREEN + "Erkennungsradius: " + radius + " Blöcke"); // Nur Klicks auf Slots 11, 15 und 22 verarbeiten
meta.setLore(Arrays.asList( if (slot != 11 && slot != 15 && slot != 22) return;
ChatColor.GRAY + "Linksklick: +0.5 Blöcke",
ChatColor.GRAY + "Rechtsklick: -0.5 Blöcke" double radius = dataManager.getMotionSensorRadius(blockLocation);
)); if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0);
clicked.setItemMeta(meta); long delay = dataManager.getMotionSensorDelay(blockLocation);
} else if (clicked.getType() == Material.CLOCK) { if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L);
if (event.isLeftClick()) {
delay = Math.min(delay + 1000, 30000); // Max. Verzögerung: 30 Sekunden if (clicked.getType() == Material.COMPASS && slot == 11) {
} else if (event.isRightClick()) { if (event.isLeftClick()) {
delay = Math.max(delay - 1000, 1000); // Min. Verzögerung: 1 Sekunde radius = Math.min(radius + 0.5, 20.0); // Max. Radius: 20 Blöcke
} } else if (event.isRightClick()) {
dataManager.setMotionSensorDelay(blockLocation, delay); radius = Math.max(radius - 0.5, 0.5); // Min. Radius: 0.5 Blöcke
ItemMeta meta = clicked.getItemMeta(); }
meta.setDisplayName(ChatColor.GREEN + "Schließverzögerung: " + (delay / 1000.0) + " Sekunden"); dataManager.setMotionSensorRadius(blockLocation, radius);
meta.setLore(Arrays.asList( ItemMeta meta = clicked.getItemMeta();
ChatColor.GRAY + "Linksklick: +1 Sekunde", meta.setDisplayName(ChatColor.GREEN + "Erkennungsradius: " + radius + " Blöcke");
ChatColor.GRAY + "Rechtsklick: -1 Sekunde" meta.setLore(Arrays.asList(
)); ChatColor.GRAY + "Linksklick: +0.5 Blöcke",
clicked.setItemMeta(meta); ChatColor.GRAY + "Rechtsklick: -0.5 Blöcke"
} else if (clicked.getType() == Material.EMERALD) { ));
player.closeInventory(); clicked.setItemMeta(meta);
} inv.setItem(11, clicked);
} } else if (clicked.getType() == Material.CLOCK && slot == 15) {
if (event.isLeftClick()) {
@EventHandler delay = Math.min(delay + 1000, 30000); // Max. Verzögerung: 30 Sekunden
public void onInventoryClose(InventoryCloseEvent event) { } else if (event.isRightClick()) {
if (event.getPlayer().equals(player)) { delay = Math.max(delay - 1000, 1000); // Min. Verzögerung: 1 Sekunde
InventoryClickEvent.getHandlerList().unregister(this); }
InventoryCloseEvent.getHandlerList().unregister(this); dataManager.setMotionSensorDelay(blockLocation, delay);
} ItemMeta meta = clicked.getItemMeta();
} meta.setDisplayName(ChatColor.GREEN + "Schließverzögerung: " + (delay / 1000.0) + " Sekunden");
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Linksklick: +1 Sekunde",
ChatColor.GRAY + "Rechtsklick: -1 Sekunde"
));
clicked.setItemMeta(meta);
inv.setItem(15, clicked);
} else if (clicked.getType() == Material.EMERALD && slot == 22) {
player.closeInventory();
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (!event.getInventory().equals(inv) || !event.getWhoClicked().equals(player)) return;
event.setCancelled(true); // Verhindert Drag-and-Drop
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (event.getPlayer().equals(player) && event.getInventory().equals(inv)) {
InventoryClickEvent.getHandlerList().unregister(this);
InventoryDragEvent.getHandlerList().unregister(this);
InventoryCloseEvent.getHandlerList().unregister(this);
}
}
} }

View File

@@ -0,0 +1,506 @@
package viper;
import org.bukkit.configuration.file.FileConfiguration;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MySQLStorage {
private final ButtonControl plugin;
private final String host;
private final int port;
private final String database;
private final String user;
private final String password;
private final boolean enabled;
private Connection connection;
public MySQLStorage(ButtonControl plugin) {
this.plugin = plugin;
FileConfiguration cfg = plugin.getConfigManager().getConfig();
this.enabled = cfg.getBoolean("mysql.enabled", false);
this.host = cfg.getString("mysql.host", "127.0.0.1");
this.port = cfg.getInt("mysql.port", 3306);
this.database = cfg.getString("mysql.database", "buttoncontrol");
this.user = cfg.getString("mysql.user", "root");
this.password = cfg.getString("mysql.password", "");
}
public boolean initialize() {
if (!enabled) return false;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
getConnection();
createTables();
plugin.getLogger().info("MySQL aktiviert.");
return true;
} catch (Exception e) {
plugin.getLogger().warning("MySQL konnte nicht initialisiert werden, verwende data.yml: " + e.getMessage());
return false;
}
}
public void close() {
if (connection != null) {
try {
connection.close();
} catch (SQLException ignored) {
}
connection = null;
}
}
private Connection getConnection() throws SQLException {
if (connection != null && connection.isValid(2)) {
return connection;
}
String url = "jdbc:mysql://" + host + ":" + port + "/" + database
+ "?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
return connection;
}
private void createTables() throws SQLException {
try (Statement st = getConnection().createStatement()) {
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_controllers ("
+ "location VARCHAR(128) PRIMARY KEY,"
+ "owner_uuid VARCHAR(36) NOT NULL,"
+ "button_id VARCHAR(64) NOT NULL"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_button_connections ("
+ "owner_uuid VARCHAR(36) NOT NULL,"
+ "button_id VARCHAR(64) NOT NULL,"
+ "block_location VARCHAR(128) NOT NULL,"
+ "PRIMARY KEY (button_id, block_location)"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_controller_names ("
+ "button_id VARCHAR(64) PRIMARY KEY,"
+ "name VARCHAR(64)"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_schedules ("
+ "button_id VARCHAR(64) PRIMARY KEY,"
+ "open_time BIGINT,"
+ "close_time BIGINT"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_trust ("
+ "button_id VARCHAR(64) NOT NULL,"
+ "target_uuid VARCHAR(36) NOT NULL,"
+ "PRIMARY KEY (button_id, target_uuid)"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_public_status ("
+ "button_id VARCHAR(64) PRIMARY KEY,"
+ "is_public BOOLEAN NOT NULL"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_player_settings ("
+ "player_uuid VARCHAR(36) PRIMARY KEY,"
+ "instrument VARCHAR(32)"
+ ")");
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_motion_sensors ("
+ "location VARCHAR(128) PRIMARY KEY,"
+ "radius DOUBLE,"
+ "delay_ms BIGINT"
+ ")");
}
}
public boolean canAccess(String buttonId, UUID playerUUID) {
return isPublic(buttonId) || isOwner(buttonId, playerUUID) || isTrusted(buttonId, playerUUID);
}
public boolean isOwner(String buttonId, UUID playerUUID) {
String uuid = playerUUID.toString();
String q1 = "SELECT 1 FROM bc_controllers WHERE button_id = ? AND owner_uuid = ? LIMIT 1";
String q2 = "SELECT 1 FROM bc_button_connections WHERE button_id = ? AND owner_uuid = ? LIMIT 1";
try (PreparedStatement ps1 = getConnection().prepareStatement(q1);
PreparedStatement ps2 = getConnection().prepareStatement(q2)) {
ps1.setString(1, buttonId);
ps1.setString(2, uuid);
try (ResultSet rs = ps1.executeQuery()) {
if (rs.next()) return true;
}
ps2.setString(1, buttonId);
ps2.setString(2, uuid);
try (ResultSet rs = ps2.executeQuery()) {
return rs.next();
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL isOwner Fehler: " + e.getMessage());
return false;
}
}
public String getButtonIdForLocation(String location) {
String q = "SELECT button_id FROM bc_controllers WHERE location = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getString(1) : null;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getButtonIdForLocation Fehler: " + e.getMessage());
return null;
}
}
public void registerController(String location, UUID ownerUUID, String buttonId) {
String q = "INSERT INTO bc_controllers (location, owner_uuid, button_id) VALUES (?, ?, ?)"
+ " ON DUPLICATE KEY UPDATE owner_uuid = VALUES(owner_uuid), button_id = VALUES(button_id)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
ps.setString(2, ownerUUID.toString());
ps.setString(3, buttonId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL registerController Fehler: " + e.getMessage());
}
}
public void removeController(String location) {
// buttonId vor dem Löschen ermitteln
String buttonId = getButtonIdForLocation(location);
String q = "DELETE FROM bc_controllers WHERE location = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL removeController Fehler: " + e.getMessage());
}
// Alle zugehörigen Tabellen bereinigen
if (buttonId != null) {
deleteButtonData(buttonId);
}
removeMotionSensorSettings(location);
}
private void deleteButtonData(String buttonId) {
String[] queries = {
"DELETE FROM bc_controller_names WHERE button_id = ?",
"DELETE FROM bc_public_status WHERE button_id = ?",
"DELETE FROM bc_trust WHERE button_id = ?",
"DELETE FROM bc_schedules WHERE button_id = ?",
"DELETE FROM bc_button_connections WHERE button_id = ?"
};
for (String q : queries) {
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL deleteButtonData Fehler: " + e.getMessage());
}
}
}
public String getButtonIdForPlacedController(String location) {
return getButtonIdForLocation(location);
}
public List<String> getAllPlacedControllers() {
List<String> result = new ArrayList<>();
String q = "SELECT location FROM bc_controllers";
try (PreparedStatement ps = getConnection().prepareStatement(q);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) result.add(rs.getString(1));
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getAllPlacedControllers Fehler: " + e.getMessage());
}
return result;
}
public void setConnectedBlocks(String playerUUID, String buttonId, List<String> blocks) {
String del = "DELETE FROM bc_button_connections WHERE owner_uuid = ? AND button_id = ?";
String ins = "INSERT INTO bc_button_connections (owner_uuid, button_id, block_location) VALUES (?, ?, ?)";
try (PreparedStatement psDel = getConnection().prepareStatement(del);
PreparedStatement psIns = getConnection().prepareStatement(ins)) {
psDel.setString(1, playerUUID);
psDel.setString(2, buttonId);
psDel.executeUpdate();
for (String block : blocks) {
psIns.setString(1, playerUUID);
psIns.setString(2, buttonId);
psIns.setString(3, block);
psIns.addBatch();
}
psIns.executeBatch();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setConnectedBlocks Fehler: " + e.getMessage());
}
}
public List<String> getConnectedBlocks(String buttonId) {
List<String> result = new ArrayList<>();
String q = "SELECT block_location FROM bc_button_connections WHERE button_id = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) result.add(rs.getString(1));
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getConnectedBlocks Fehler: " + e.getMessage());
}
return result;
}
public boolean removeFromAllConnectedBlocks(String locStr) {
String q = "DELETE FROM bc_button_connections WHERE block_location = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, locStr);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
plugin.getLogger().warning("MySQL removeFromAllConnectedBlocks Fehler: " + e.getMessage());
return false;
}
}
public void setControllerName(String buttonId, String name) {
String q = "INSERT INTO bc_controller_names (button_id, name) VALUES (?, ?)"
+ " ON DUPLICATE KEY UPDATE name = VALUES(name)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setString(2, name);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setControllerName Fehler: " + e.getMessage());
}
}
public String getControllerName(String buttonId) {
String q = "SELECT name FROM bc_controller_names WHERE button_id = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getString(1) : null;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getControllerName Fehler: " + e.getMessage());
return null;
}
}
public void setScheduleOpenTime(String buttonId, long ticks) {
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time) VALUES (?, ?, COALESCE((SELECT close_time FROM bc_schedules WHERE button_id = ?), -1))"
+ " ON DUPLICATE KEY UPDATE open_time = VALUES(open_time)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setLong(2, ticks);
ps.setString(3, buttonId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setScheduleOpenTime Fehler: " + e.getMessage());
}
}
public long getScheduleOpenTime(String buttonId) {
String q = "SELECT open_time FROM bc_schedules WHERE button_id = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getLong(1) : -1;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getScheduleOpenTime Fehler: " + e.getMessage());
return -1;
}
}
public void setScheduleCloseTime(String buttonId, long ticks) {
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time) VALUES (?, COALESCE((SELECT open_time FROM bc_schedules WHERE button_id = ?), -1), ?)"
+ " ON DUPLICATE KEY UPDATE close_time = VALUES(close_time)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setString(2, buttonId);
ps.setLong(3, ticks);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setScheduleCloseTime Fehler: " + e.getMessage());
}
}
public long getScheduleCloseTime(String buttonId) {
String q = "SELECT close_time FROM bc_schedules WHERE button_id = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getLong(1) : -1;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getScheduleCloseTime Fehler: " + e.getMessage());
return -1;
}
}
public void clearSchedule(String buttonId) {
String q = "DELETE FROM bc_schedules WHERE button_id = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL clearSchedule Fehler: " + e.getMessage());
}
}
public void addTrustedPlayer(String buttonId, UUID targetUUID) {
String q = "INSERT IGNORE INTO bc_trust (button_id, target_uuid) VALUES (?, ?)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setString(2, targetUUID.toString());
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL addTrustedPlayer Fehler: " + e.getMessage());
}
}
public void removeTrustedPlayer(String buttonId, UUID targetUUID) {
String q = "DELETE FROM bc_trust WHERE button_id = ? AND target_uuid = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setString(2, targetUUID.toString());
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL removeTrustedPlayer Fehler: " + e.getMessage());
}
}
public void setPublic(String buttonId, boolean isPublic) {
String q = "INSERT INTO bc_public_status (button_id, is_public) VALUES (?, ?)"
+ " ON DUPLICATE KEY UPDATE is_public = VALUES(is_public)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setBoolean(2, isPublic);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setPublic Fehler: " + e.getMessage());
}
}
public boolean isPublic(String buttonId) {
String q = "SELECT is_public FROM bc_public_status WHERE button_id = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() && rs.getBoolean(1);
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL isPublic Fehler: " + e.getMessage());
return false;
}
}
public void setPlayerInstrument(UUID playerUUID, String instrument) {
String q = "INSERT INTO bc_player_settings (player_uuid, instrument) VALUES (?, ?)"
+ " ON DUPLICATE KEY UPDATE instrument = VALUES(instrument)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, playerUUID.toString());
ps.setString(2, instrument);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setPlayerInstrument Fehler: " + e.getMessage());
}
}
public String getPlayerInstrument(UUID playerUUID) {
String q = "SELECT instrument FROM bc_player_settings WHERE player_uuid = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, playerUUID.toString());
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getString(1) : null;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getPlayerInstrument Fehler: " + e.getMessage());
return null;
}
}
public void setMotionSensorRadius(String location, double radius) {
String q = "INSERT INTO bc_motion_sensors (location, radius, delay_ms) VALUES (?, ?, COALESCE((SELECT delay_ms FROM bc_motion_sensors WHERE location = ?), -1))"
+ " ON DUPLICATE KEY UPDATE radius = VALUES(radius)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
ps.setDouble(2, radius);
ps.setString(3, location);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setMotionSensorRadius Fehler: " + e.getMessage());
}
}
public double getMotionSensorRadius(String location) {
String q = "SELECT radius FROM bc_motion_sensors WHERE location = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getDouble(1) : -1;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getMotionSensorRadius Fehler: " + e.getMessage());
return -1;
}
}
public void setMotionSensorDelay(String location, long delay) {
String q = "INSERT INTO bc_motion_sensors (location, radius, delay_ms) VALUES (?, COALESCE((SELECT radius FROM bc_motion_sensors WHERE location = ?), -1), ?)"
+ " ON DUPLICATE KEY UPDATE delay_ms = VALUES(delay_ms)";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
ps.setString(2, location);
ps.setLong(3, delay);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL setMotionSensorDelay Fehler: " + e.getMessage());
}
}
public long getMotionSensorDelay(String location) {
String q = "SELECT delay_ms FROM bc_motion_sensors WHERE location = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? rs.getLong(1) : -1;
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL getMotionSensorDelay Fehler: " + e.getMessage());
return -1;
}
}
public void removeMotionSensorSettings(String location) {
String q = "DELETE FROM bc_motion_sensors WHERE location = ?";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, location);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("MySQL removeMotionSensorSettings Fehler: " + e.getMessage());
}
}
private boolean isTrusted(String buttonId, UUID playerUUID) {
String q = "SELECT 1 FROM bc_trust WHERE button_id = ? AND target_uuid = ? LIMIT 1";
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
ps.setString(1, buttonId);
ps.setString(2, playerUUID.toString());
try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}
} catch (SQLException e) {
plugin.getLogger().warning("MySQL isTrusted Fehler: " + e.getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,216 @@
package viper;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* GUI zur Konfiguration der zeitgesteuerten Automatisierung eines Controllers.
*
* Layout (9×3 = 27 Slots):
* Slot 10 Öffnungszeit (LIME_DYE / Sonne) ← Links/Rechts: ±1h | Shift: ±15min
* Slot 13 Aktivierung an/aus (LEVER)
* Slot 16 Schließzeit (RED_DYE / Mond) ← Links/Rechts: ±1h | Shift: ±15min
* Slot 22 Speichern & Schließen (EMERALD)
*
* Zeit wird als Ingame-Ticks gespeichert (023999).
* ticksToTime() / timeToTicks() in ButtonControl konvertieren nach "HH:MM".
*/
public class ScheduleGUI implements Listener {
private final ButtonControl plugin;
private final DataManager dataManager;
private final Player player;
private final String buttonId;
private final Inventory inv;
// Aktuelle Werte während die GUI offen ist
private long openTime;
private long closeTime;
private boolean enabled;
public ScheduleGUI(ButtonControl plugin, Player player, String buttonId) {
this.plugin = plugin;
this.dataManager = plugin.getDataManager();
this.player = player;
this.buttonId = buttonId;
this.inv = Bukkit.createInventory(null, 27, "§6Zeitplan-Einstellungen");
// Gespeicherte Werte laden (oder Standardwerte)
long savedOpen = dataManager.getScheduleOpenTime(buttonId);
long savedClose = dataManager.getScheduleCloseTime(buttonId);
this.openTime = savedOpen >= 0 ? savedOpen : plugin.timeToTicks(7, 0); // 07:00
this.closeTime = savedClose >= 0 ? savedClose : plugin.timeToTicks(19, 0); // 19:00
this.enabled = savedOpen >= 0;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
public void open() {
renderItems();
player.openInventory(inv);
}
// -----------------------------------------------------------------------
// GUI aufbauen
// -----------------------------------------------------------------------
private void renderItems() {
// Füllung
ItemStack filler = makeItem(Material.GRAY_STAINED_GLASS_PANE, ChatColor.RESET + "");
for (int i = 0; i < 27; i++) inv.setItem(i, filler);
// Slot 10 Öffnungszeit
inv.setItem(10, makeTimeItem(
Material.LIME_DYE,
"§a§lÖffnungszeit",
openTime,
"§7Linksklick: §f+1 Stunde",
"§7Rechtsklick: §f1 Stunde",
"§7Shift+Links: §f+15 Minuten",
"§7Shift+Rechts: §f15 Minuten"
));
// Slot 13 Aktivierung an/aus
Material leverMat = enabled ? Material.LEVER : Material.DEAD_BUSH;
String leverName = enabled ? "§a§lZeitplan aktiv" : "§c§lZeitplan deaktiviert";
String leverDesc = enabled ? "§7Klick zum §cDeaktivieren" : "§7Klick zum §aAktivieren";
inv.setItem(13, makeItem(leverMat, leverName, leverDesc, "§8(Zeitplan wird gespeichert)"));
// Slot 16 Schließzeit
inv.setItem(16, makeTimeItem(
Material.RED_DYE,
"§c§lSchließzeit",
closeTime,
"§7Linksklick: §f+1 Stunde",
"§7Rechtsklick: §f1 Stunde",
"§7Shift+Links: §f+15 Minuten",
"§7Shift+Rechts: §f15 Minuten"
));
// Slot 22 Speichern
inv.setItem(22, makeItem(Material.EMERALD,
"§a§lSpeichern & Schließen",
"§7Speichert den aktuellen Zeitplan."));
}
private ItemStack makeTimeItem(Material mat, String name, long ticks, String... loreLines) {
String timeStr = plugin.ticksToTime(ticks);
List<String> lore = new ArrayList<>();
lore.add("§e§l" + timeStr + " Uhr §7(Ingame)");
lore.add("");
lore.addAll(Arrays.asList(loreLines));
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
}
private ItemStack makeItem(Material mat, String name, String... lore) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(name);
meta.setLore(Arrays.asList(lore));
item.setItemMeta(meta);
}
return item;
}
// -----------------------------------------------------------------------
// Event-Handler
// -----------------------------------------------------------------------
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!event.getInventory().equals(inv)) return;
if (!event.getWhoClicked().equals(player)) return;
event.setCancelled(true);
ItemStack clicked = event.getCurrentItem();
if (clicked == null || clicked.getType() == Material.GRAY_STAINED_GLASS_PANE) return;
int slot = event.getRawSlot();
// Nur Klicks in unserer GUI (026) verarbeiten
if (slot < 0 || slot > 26) return;
// Schrittgröße: Shift = 15 Min (250 Ticks), sonst 1 Std (1000 Ticks)
long step = event.isShiftClick() ? 250L : 1000L;
if (slot == 10) {
// Öffnungszeit anpassen
if (event.isLeftClick()) openTime = (openTime + step + 24000) % 24000;
if (event.isRightClick()) openTime = (openTime - step + 24000) % 24000;
inv.setItem(10, makeTimeItem(Material.LIME_DYE, "§a§lÖffnungszeit", openTime,
"§7Linksklick: §f+1 Stunde", "§7Rechtsklick: §f1 Stunde",
"§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f15 Minuten"));
} else if (slot == 13) {
// Aktivierung umschalten
enabled = !enabled;
renderItems();
} else if (slot == 16) {
// Schließzeit anpassen
if (event.isLeftClick()) closeTime = (closeTime + step + 24000) % 24000;
if (event.isRightClick()) closeTime = (closeTime - step + 24000) % 24000;
inv.setItem(16, makeTimeItem(Material.RED_DYE, "§c§lSchließzeit", closeTime,
"§7Linksklick: §f+1 Stunde", "§7Rechtsklick: §f1 Stunde",
"§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f15 Minuten"));
} else if (slot == 22) {
// Speichern
save();
player.closeInventory();
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (event.getInventory().equals(inv) && event.getWhoClicked().equals(player))
event.setCancelled(true);
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (!event.getPlayer().equals(player) || !event.getInventory().equals(inv)) return;
InventoryClickEvent.getHandlerList().unregister(this);
InventoryDragEvent.getHandlerList().unregister(this);
InventoryCloseEvent.getHandlerList().unregister(this);
}
// -----------------------------------------------------------------------
// Speichern
// -----------------------------------------------------------------------
private void save() {
if (enabled) {
dataManager.setScheduleOpenTime(buttonId, openTime);
dataManager.setScheduleCloseTime(buttonId, closeTime);
player.sendMessage("§a[BC] §7Zeitplan gespeichert: §aÖffnet §7um §e"
+ plugin.ticksToTime(openTime)
+ " §7· §cSchließt §7um §e"
+ plugin.ticksToTime(closeTime));
} else {
dataManager.clearSchedule(buttonId);
player.sendMessage("§7[BC] Zeitplan deaktiviert.");
}
}
}

View File

@@ -1,4 +1,6 @@
# Maximale Anzahl an steuerbaren Blöcken pro Controller # ButtonControl Konfiguration
# ── Maximale Anzahl verbundener Blöcke pro Controller ──────────────────────
max-doors: 20 max-doors: 20
max-lamps: 50 max-lamps: 50
max-noteblocks: 10 max-noteblocks: 10
@@ -6,13 +8,46 @@ max-gates: 20
max-trapdoors: 20 max-trapdoors: 20
max-bells: 5 max-bells: 5
# Standard-Instrument für Notenblöcke # ── Notenblöcke ─────────────────────────────────────────────────────────────
default-note: "PIANO" default-note: "PIANO"
# Doppelter Notenblock-Ton
double-note-enabled: true double-note-enabled: true
double-note-delay-ms: 1000 double-note-delay-ms: 1000
# Bewegungsmelder-Einstellungen # ── Bewegungsmelder ──────────────────────────────────────────────────────────
# Gilt als Standardwert wenn für einen Sensor nichts individuell gesetzt wurde
motion-detection-radius: 5.0 motion-detection-radius: 5.0
motion-close-delay-ms: 5000 motion-close-delay-ms: 5000
# Cooldown: wie lange nach dem Schließen der Sensor nicht erneut auslöst
motion-trigger-cooldown-ms: 2000
# ── Optionales MySQL-Backend ────────────────────────────────────────────────
mysql:
# false = data.yml verwenden, true = MySQL verwenden
enabled: false
host: "127.0.0.1"
port: 3306
database: "controll"
user: "controll"
password: "controll"
# ── Controller-Name beim Anschauen ──────────────────────────────────────────
controller-name-display:
enabled: true
# Distanz in Blöcken für die Blickerkennung
max-look-distance: 8
# %s = Name aus /bc rename
format: "§6Controller: §f%s"
# ── Sounds beim Öffnen/Schließen (NEU) ──────────────────────────────────────
# Auf false setzen um alle Controller-Sounds zu deaktivieren
sounds:
enabled: true
# Holztüren, Zauntore, Falltüren
door-open: BLOCK_WOODEN_DOOR_OPEN
door-close: BLOCK_WOODEN_DOOR_CLOSE
# Eisentüren und Eisenfalltüren
iron-door-open: BLOCK_IRON_DOOR_OPEN
iron-door-close: BLOCK_IRON_DOOR_CLOSE
# Redstone-Lampen
lamp-on: BLOCK_LEVER_CLICK
lamp-off: BLOCK_LEVER_CLICK

View File

@@ -1,39 +1,64 @@
# lang.yml ButtonControl Nachrichten
# ── Holztüren ────────────────────────────────────────────────────────────────
tueren-geoeffnet: "§aTüren wurden geöffnet." tueren-geoeffnet: "§aTüren wurden geöffnet."
tueren-geschlossen: "§cTüren wurden geschlossen." tueren-geschlossen: "§cTüren wurden geschlossen."
max-tueren-erreicht: "§cMaximale Anzahl an Türen erreicht."
# ── Eisentüren (NEU) ─────────────────────────────────────────────────────────
eisentueren-geoeffnet: "§aEisentüren wurden geöffnet."
eisentueren-geschlossen: "§cEisentüren wurden geschlossen."
eisenfallturen-geoeffnet: "§aEisen-Falltüren wurden geöffnet."
eisenfallturen-geschlossen: "§cEisen-Falltüren wurden geschlossen."
# ── Zauntore ─────────────────────────────────────────────────────────────────
gates-geoeffnet: "§aZauntore wurden geöffnet." gates-geoeffnet: "§aZauntore wurden geöffnet."
gates-geschlossen: "§cZauntore wurden geschlossen." gates-geschlossen: "§cZauntore wurden geschlossen."
max-gates-erreicht: "§cMaximale Anzahl an Zauntoren erreicht." max-gates-erreicht: "§cMaximale Anzahl an Zauntoren erreicht."
# ── Falltüren ────────────────────────────────────────────────────────────────
fallturen-geoeffnet: "§aFalltüren wurden geöffnet." fallturen-geoeffnet: "§aFalltüren wurden geöffnet."
fallturen-geschlossen: "§cFalltüren wurden geschlossen." fallturen-geschlossen: "§cFalltüren wurden geschlossen."
max-fallturen-erreicht: "§cMaximale Anzahl an Falltüren erreicht." max-fallturen-erreicht: "§cMaximale Anzahl an Falltüren erreicht."
# ── Lampen & Glocken ─────────────────────────────────────────────────────────
lampen-eingeschaltet: "§aLampen wurden eingeschaltet." lampen-eingeschaltet: "§aLampen wurden eingeschaltet."
lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet." lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet."
bloecke-umgeschaltet: "§eBlöcke wurden umgeschaltet."
keine-bloecke-verbunden: "§cKeine Blöcke sind verbunden."
max-tueren-erreicht: "§cMaximale Anzahl an Türen erreicht."
max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht." max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht."
glocke-gelaeutet: "§aGlocke wurde geläutet."
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht."
# ── Notenblöcke ──────────────────────────────────────────────────────────────
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst."
instrument-gesetzt: "§aDein Instrument wurde auf %s gesetzt."
ungueltiges-instrument: "§cUngültiges Instrument! Verwende: /bc note <Instrument>"
max-notenbloecke-erreicht: "§cMaximale Anzahl an Notenblöcken erreicht." max-notenbloecke-erreicht: "§cMaximale Anzahl an Notenblöcken erreicht."
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht." # ── Kolben (vorbereitet für zukünftige Erweiterung) ─────────────────────────
glocke-gelaeutet: "§aGlocke wurde geläutet." kolben-ausgefahren: "§6[ButtonControl] §7Kolben wurden ausgefahren."
kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingezogen."
max-kolben-erreicht: "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."
# ── Controller & Verbindung ──────────────────────────────────────────────────
block-verbunden: "§aBlock verbunden." block-verbunden: "§aBlock verbunden."
block-bereits-verbunden: "§cBlock ist bereits verbunden." block-bereits-verbunden: "§cBlock ist bereits verbunden."
block-verbindung-entfernt: "§7Verbindung zu abgebautem Block automatisch entfernt."
keine-bloecke-verbunden: "§cKeine Blöcke sind verbunden."
bloecke-umgeschaltet: "§eBlöcke wurden umgeschaltet."
controller-platziert: "§aController platziert." controller-platziert: "§aController platziert."
controller-entfernt: "§cController entfernt." controller-entfernt: "§cController entfernt."
controller-umbenannt: "§aController umbenannt zu: §f%s"
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst." # ── Trust- & Sicherheitssystem ───────────────────────────────────────────────
instrument-gesetzt: "§aDein Notenblock-Instrument wurde auf %s gesetzt."
ungueltiges-instrument: "§cUngültiges Instrument! Verwende: /bc note <Instrument>"
konfiguration-neugeladen: "§aKonfiguration und Daten erfolgreich neu geladen!"
keine-berechtigung: "§cDu hast keine Berechtigung für diesen Befehl!" keine-berechtigung: "§cDu hast keine Berechtigung für diesen Befehl!"
keine-berechtigung-controller: "§cDu darfst diesen Controller nicht benutzen!"
nur-besitzer-abbauen: "§cNur der Besitzer kann diesen Controller verwalten!"
spieler-nicht-gefunden: "§cDieser Spieler wurde nicht gefunden."
# %s = "§aÖffentlich" oder "§cPrivat"
status-geandert: "§6[BC] §7Controller ist nun %s§7."
trust-hinzugefuegt: "§a%s darf diesen Controller nun benutzen."
trust-entfernt: "§c%s wurde das Vertrauen entzogen."
kein-controller-im-blick: "§cBitte sieh einen Controller direkt an!"
kolben-ausgefahren: "§6[ButtonControl] §7Kolben wurden ausgefahren." # ── System ───────────────────────────────────────────────────────────────────
kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingefahren." konfiguration-neugeladen: "§aKonfiguration und Daten erfolgreich neu geladen!"
max-kolben-erreicht: "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."

View File

@@ -1,20 +1,32 @@
name: ButtonControl name: ButtonControl
version: 1.3 version: 1.7
main: viper.ButtonControl main: viper.ButtonControl
api-version: 1.21 api-version: 1.21
author: viper author: M_Viper
description: Ein Plugin, um Türen, Redstone-Lampen und Notenblöcke mit einem Button oder Tageslichtsensor zu steuern. description: >
Steuert Türen, Eisentüren, Lampen, Notenblöcke und Glocken mit Buttons,
Tageslichtsensoren, Bewegungsmeldern, Teppich-Sensoren und Schildern.
Unterstützt Zeitpläne, Trust-System und Admin-Bypass.
commands: commands:
bc: bc:
description: Steuert das ButtonControl Plugin description: Hauptbefehl für ButtonControl
usage: /<command> [info|reload|note <instrument>] usage: /bc <info|reload|note|list|rename|schedule|trust|untrust|public|private>
aliases: [buttoncontrol] aliases: [buttoncontrol]
permissions: permissions:
buttoncontrol.admin:
description: Admin-Bypass Zugriff auf und Verwaltung aller Controller
default: op
buttoncontrol.reload: buttoncontrol.reload:
description: Erlaubt das Neuladen der Plugin-Konfiguration description: Konfiguration neu laden
default: op default: op
buttoncontrol.note: buttoncontrol.note:
description: Erlaubt das Ändern des Notenblock-Instruments description: Notenblock-Instrument ändern
default: true
buttoncontrol.update:
description: Update-Benachrichtigungen empfangen
default: op
buttoncontrol.trust:
description: Trust-System und Public/Private-Status verwalten
default: true default: true