Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf112e6877 | |||
| a9d2d7a911 | |||
| e351990d74 | |||
| fbeead1900 | |||
| 1f9a2c2198 | |||
| 9ef3e7e14b | |||
| c7eba7d902 | |||
| 77c9e1fcfc | |||
| 4d1901c03a | |||
| 99a817d33d | |||
| 3322a500e1 | |||
| 7e197a9ac6 | |||
| 63988befeb | |||
| 120245c2ad |
509
README.md
509
README.md
@@ -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.*
|
||||||
12
pom.xml
12
pom.xml
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<groupId>viper</groupId>
|
<groupId>viper</groupId>
|
||||||
<artifactId>ButtonControl</artifactId>
|
<artifactId>ButtonControl</artifactId>
|
||||||
<version>1.5</version>
|
<version>1.9</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>ButtonControl</name>
|
<name>ButtonControl</name>
|
||||||
|
|
||||||
@@ -29,11 +29,12 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- bStats Bukkit -->
|
<!-- bStats Bukkit (optional) -->
|
||||||
<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>
|
||||||
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- org.json für UpdateChecker JSON-Parsing -->
|
<!-- org.json für UpdateChecker JSON-Parsing -->
|
||||||
@@ -42,6 +43,13 @@
|
|||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
<version>20240303</version>
|
<version>20240303</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL Connector/J für optionales Datenbank-Backend -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>8.4.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,9 @@ package viper;
|
|||||||
import org.bukkit.Bukkit;
|
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.entity.Player;
|
||||||
@@ -13,6 +15,7 @@ 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;
|
||||||
|
|
||||||
@@ -26,42 +29,68 @@ 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) {
|
||||||
Player player = event.getPlayer();
|
// Doppelt-Feuern bei Items verhindern (Haupt- und Nebenhand)
|
||||||
UUID playerUUID = player.getUniqueId();
|
if (event.getHand() != EquipmentSlot.HAND) return;
|
||||||
ItemStack item = event.getItem();
|
|
||||||
Block block = event.getClickedBlock();
|
|
||||||
|
|
||||||
// 1. Logik für bereits platzierte Controller (Benutzung)
|
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.getButtonIdForLocation(blockLocation);
|
String buttonId = dataManager.getButtonIdForLocation(blockLocation);
|
||||||
|
|
||||||
if (buttonId != null) {
|
if (buttonId != null) {
|
||||||
if (!dataManager.canAccess(buttonId, playerUUID)) {
|
// Admin-Bypass
|
||||||
|
if (!dataManager.canAccess(buttonId, playerUUID)
|
||||||
|
&& !player.hasPermission("buttoncontrol.admin")) {
|
||||||
player.sendMessage(configManager.getMessage("keine-berechtigung-controller"));
|
player.sendMessage(configManager.getMessage("keine-berechtigung-controller"));
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.getType() == Material.TRIPWIRE_HOOK) {
|
// Tripwire & Teppich → MotionSensorGUI öffnen
|
||||||
|
if (block.getType() == Material.TRIPWIRE_HOOK
|
||||||
|
|| block.getType().name().endsWith("_CARPET")) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
new MotionSensorGUI(plugin, player, blockLocation, buttonId).open();
|
new MotionSensorGUI(plugin, player, blockLocation, buttonId).open();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isButton(block.getType()) || block.getType() == Material.DAYLIGHT_DETECTOR) {
|
// 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);
|
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
|
||||||
|
boolean hasConnectedBlocks = connectedBlocks != null && !connectedBlocks.isEmpty();
|
||||||
if (connectedBlocks != null && !connectedBlocks.isEmpty()) {
|
boolean secretTriggered = plugin.triggerSecretWall(buttonId);
|
||||||
|
|
||||||
|
if (hasConnectedBlocks) {
|
||||||
toggleConnectedBlocks(player, playerUUID, connectedBlocks);
|
toggleConnectedBlocks(player, playerUUID, connectedBlocks);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!hasConnectedBlocks && !secretTriggered) {
|
||||||
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
|
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,207 +98,407 @@ public class ButtonListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Logik für das Verbinden von neuen Blöcken (Item in der Hand)
|
// ── 2. Verbinden mit Controller-Item in der Hand ───────────────────
|
||||||
if (item == null || !item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) {
|
if (item == null || !item.hasItemMeta()) return;
|
||||||
return;
|
String displayName = item.getItemMeta().getDisplayName();
|
||||||
}
|
if (!displayName.contains("§6Steuer-")) return;
|
||||||
|
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) return;
|
||||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInteractableTarget(block.getType())) {
|
if (isInteractableTarget(block.getType())) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
||||||
ItemMeta meta = item.getItemMeta();
|
// Doppeltür: immer untersten Block speichern
|
||||||
String buttonId = null;
|
Block targetBlock = getBottomDoorBlock(block);
|
||||||
|
|
||||||
// ID aus der Lore extrahieren (wir suchen nach dem Präfix §8ID: )
|
ItemMeta meta = item.getItemMeta();
|
||||||
if (meta != null && meta.hasLore() && !meta.getLore().isEmpty()) {
|
String buttonId = extractButtonId(meta);
|
||||||
String firstLine = meta.getLore().get(0);
|
|
||||||
if (firstLine.startsWith("§8ID: ")) {
|
|
||||||
buttonId = firstLine.replace("§8ID: ", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Falls keine ID existiert (neues Item), generieren und SOFORT speichern
|
|
||||||
if (buttonId == null) {
|
if (buttonId == null) {
|
||||||
buttonId = UUID.randomUUID().toString();
|
buttonId = UUID.randomUUID().toString();
|
||||||
updateButtonLore(item, buttonId);
|
updateButtonLore(item, buttonId);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
|
|
||||||
if (connectedBlocks == null) {
|
|
||||||
connectedBlocks = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ();
|
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
|
||||||
if (connectedBlocks.contains(blockLocation)) {
|
if (connectedBlocks == null) connectedBlocks = new ArrayList<>();
|
||||||
|
|
||||||
|
String targetLocStr = plugin.toLoc(targetBlock);
|
||||||
|
if (connectedBlocks.contains(targetLocStr)) {
|
||||||
player.sendMessage(configManager.getMessage("block-bereits-verbunden"));
|
player.sendMessage(configManager.getMessage("block-bereits-verbunden"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkLimits(player, block.getType(), connectedBlocks)) {
|
if (checkLimits(player, targetBlock.getType(), connectedBlocks)) {
|
||||||
connectedBlocks.add(blockLocation);
|
connectedBlocks.add(targetLocStr);
|
||||||
dataManager.setConnectedBlocks(playerUUID.toString(), buttonId, connectedBlocks);
|
dataManager.setConnectedBlocks(playerUUID.toString(), buttonId, connectedBlocks);
|
||||||
player.sendMessage(configManager.getMessage("block-verbunden"));
|
player.sendMessage(configManager.getMessage("block-verbunden"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleConnectedBlocks(Player player, UUID playerUUID, List<String> connectedBlocks) {
|
// -----------------------------------------------------------------------
|
||||||
boolean anyDoorOpened = false, anyDoorClosed = false;
|
// Block-Break: Controller abbauen
|
||||||
boolean anyGateOpened = false, anyGateClosed = false;
|
// -----------------------------------------------------------------------
|
||||||
boolean anyTrapOpened = false, anyTrapClosed = false;
|
|
||||||
boolean anyLampOn = false, anyLampOff = false;
|
|
||||||
boolean anyNoteBlockPlayed = false;
|
|
||||||
boolean anyBellPlayed = false;
|
|
||||||
|
|
||||||
for (String locStr : connectedBlocks) {
|
|
||||||
Location location = parseLocation(locStr);
|
|
||||||
if (location == null) continue;
|
|
||||||
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(playerUUID);
|
|
||||||
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) player.sendMessage(configManager.getMessage("tueren-geoeffnet"));
|
|
||||||
if (anyDoorClosed) player.sendMessage(configManager.getMessage("tueren-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 boolean checkLimits(Player player, Material type, List<String> connected) {
|
|
||||||
if (isDoor(type)) {
|
|
||||||
if (connected.stream().filter(l -> isDoor(getMaterialFromLocation(l))).count() >= configManager.getMaxDoors()) {
|
|
||||||
player.sendMessage(configManager.getMessage("max-tueren-erreicht"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (isGate(type)) {
|
|
||||||
if (connected.stream().filter(l -> isGate(getMaterialFromLocation(l))).count() >= configManager.getMaxGates()) {
|
|
||||||
player.sendMessage(configManager.getMessage("max-gates-erreicht"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (isTrapdoor(type)) {
|
|
||||||
if (connected.stream().filter(l -> isTrapdoor(getMaterialFromLocation(l))).count() >= configManager.getMaxTrapdoors()) {
|
|
||||||
player.sendMessage(configManager.getMessage("max-fallturen-erreicht"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (type == Material.REDSTONE_LAMP) {
|
|
||||||
if (connected.stream().filter(l -> getMaterialFromLocation(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 -> getMaterialFromLocation(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 -> getMaterialFromLocation(l) == Material.BELL).count() >= configManager.getMaxBells()) {
|
|
||||||
player.sendMessage(configManager.getMessage("max-glocken-erreicht"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onBlockPlace(BlockPlaceEvent event) {
|
|
||||||
ItemStack item = event.getItemInHand();
|
|
||||||
if (item == null || !item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Block block = event.getBlockPlaced();
|
|
||||||
ItemMeta meta = item.getItemMeta();
|
|
||||||
String buttonId = null;
|
|
||||||
|
|
||||||
if (meta != null && meta.hasLore() && !meta.getLore().isEmpty()) {
|
|
||||||
String firstLine = meta.getLore().get(0);
|
|
||||||
if (firstLine.startsWith("§8ID: ")) {
|
|
||||||
buttonId = firstLine.replace("§8ID: ", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonId == null) {
|
|
||||||
buttonId = UUID.randomUUID().toString();
|
|
||||||
updateButtonLore(item, buttonId);
|
|
||||||
}
|
|
||||||
|
|
||||||
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ();
|
|
||||||
dataManager.registerController(blockLocation, event.getPlayer().getUniqueId(), buttonId);
|
|
||||||
event.getPlayer().sendMessage(configManager.getMessage("controller-platziert"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onBlockBreak(BlockBreakEvent event) {
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ();
|
String blockLocation = plugin.toLoc(block);
|
||||||
String buttonId = dataManager.getButtonIdForLocation(blockLocation);
|
String buttonId = dataManager.getButtonIdForLocation(blockLocation);
|
||||||
|
|
||||||
if (buttonId != null) {
|
if (buttonId != null) {
|
||||||
if (!dataManager.isOwner(buttonId, event.getPlayer().getUniqueId())) {
|
if (!dataManager.isOwner(buttonId, event.getPlayer().getUniqueId())
|
||||||
|
&& !event.getPlayer().hasPermission("buttoncontrol.admin")) {
|
||||||
event.getPlayer().sendMessage(configManager.getMessage("nur-besitzer-abbauen"));
|
event.getPlayer().sendMessage(configManager.getMessage("nur-besitzer-abbauen"));
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dataManager.removeController(blockLocation);
|
dataManager.removeController(blockLocation);
|
||||||
event.getPlayer().sendMessage(configManager.getMessage("controller-entfernt"));
|
event.getPlayer().sendMessage(configManager.getMessage("controller-entfernt"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isButton(Material m) { return m.name().endsWith("_BUTTON"); }
|
// -----------------------------------------------------------------------
|
||||||
private boolean isDoor(Material m) { return m.name().endsWith("_DOOR"); }
|
// Block-Break: Verbundener Block abgebaut → Eintrag bereinigen (NEU)
|
||||||
private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); }
|
// -----------------------------------------------------------------------
|
||||||
private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR"); }
|
|
||||||
|
@EventHandler
|
||||||
private boolean isInteractableTarget(Material m) {
|
public void onConnectedBlockBreak(BlockBreakEvent event) {
|
||||||
return isDoor(m) || isGate(m) || isTrapdoor(m) || m == Material.REDSTONE_LAMP || m == Material.NOTE_BLOCK || m == Material.BELL;
|
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 Material getMaterialFromLocation(String locString) {
|
// -----------------------------------------------------------------------
|
||||||
|
// 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// 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 anyGrateOpened = false, anyGrateClosed = false;
|
||||||
|
boolean anyCreakingHeartOn = false, anyCreakingHeartOff = false;
|
||||||
|
boolean anyNoteBlockPlayed = false;
|
||||||
|
boolean anyBellPlayed = false;
|
||||||
|
boolean anyDispenserTriggered = false;
|
||||||
|
boolean anyDropperTriggered = 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── Lampen (Redstone + Kupferlampen) ─────────────────────────
|
||||||
|
else if (isLamp(mat)) {
|
||||||
|
if (targetBlock.getBlockData() instanceof Lightable) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── Gitter (alle *_GRATE + Eisenstangen) ─────────────────────
|
||||||
|
else if (plugin.isGrate(mat) || (mat == Material.AIR && plugin.isManagedOpenGrateLocation(locStr))) {
|
||||||
|
Boolean nowOpen = plugin.toggleGrate(targetBlock);
|
||||||
|
if (nowOpen != null) {
|
||||||
|
if (nowOpen) anyGrateOpened = true;
|
||||||
|
else anyGrateClosed = 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;
|
||||||
|
}
|
||||||
|
// ── Spender / Werfer ──────────────────────────────────────────
|
||||||
|
else if (mat == Material.DISPENSER) {
|
||||||
|
if (triggerContainer(targetBlock, "dispense")) {
|
||||||
|
targetBlock.getWorld().playSound(location, Sound.BLOCK_DISPENSER_DISPENSE, 1.0f, 1.0f);
|
||||||
|
anyDispenserTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mat == Material.DROPPER) {
|
||||||
|
if (triggerContainer(targetBlock, "drop")) {
|
||||||
|
targetBlock.getWorld().playSound(location, Sound.BLOCK_DISPENSER_DISPENSE, 1.0f, 1.0f);
|
||||||
|
anyDropperTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── Creaking Heart ────────────────────────────────────────────
|
||||||
|
else if (isCreakingHeart(mat)) {
|
||||||
|
Boolean nowActive = plugin.togglePersistentCreakingHeart(targetBlock);
|
||||||
|
if (nowActive != null) {
|
||||||
|
if (nowActive) anyCreakingHeartOn = true;
|
||||||
|
else anyCreakingHeartOff = 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 (anyGrateOpened) player.sendMessage(configManager.getMessage("gitter-geoeffnet"));
|
||||||
|
if (anyGrateClosed) player.sendMessage(configManager.getMessage("gitter-geschlossen"));
|
||||||
|
if (anyCreakingHeartOn) player.sendMessage(configManager.getMessage("creaking-heart-aktiviert"));
|
||||||
|
if (anyCreakingHeartOff) player.sendMessage(configManager.getMessage("creaking-heart-deaktiviert"));
|
||||||
|
if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest"));
|
||||||
|
if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet"));
|
||||||
|
if (anyDispenserTriggered) player.sendMessage(configManager.getMessage("spender-ausgeloest"));
|
||||||
|
if (anyDropperTriggered) player.sendMessage(configManager.getMessage("werfer-ausgeloest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spielt einen Sound ab dessen Name aus der config.yml gelesen wird.
|
||||||
|
* Ist der Key nicht gesetzt oder der Sound-Name ungültig, wird der Fallback verwendet.
|
||||||
|
*/
|
||||||
|
private void playConfigSound(Location loc, String configKey, String fallback) {
|
||||||
|
String soundName = configManager.getConfig().getString(configKey, fallback);
|
||||||
|
try {
|
||||||
|
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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean triggerContainer(Block block, String methodName) {
|
||||||
|
try {
|
||||||
|
Object state = block.getState();
|
||||||
|
java.lang.reflect.Method method = state.getClass().getMethod(methodName);
|
||||||
|
Object result = method.invoke(state);
|
||||||
|
return !(result instanceof Boolean) || (Boolean) result;
|
||||||
|
} catch (ReflectiveOperationException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// 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 (isLamp(type)) {
|
||||||
|
if (connected.stream().filter(l -> isLamp(getMaterialAt(l))).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;
|
||||||
|
}
|
||||||
|
} else if (type == Material.DISPENSER) {
|
||||||
|
if (connected.stream().filter(l -> getMaterialAt(l) == Material.DISPENSER).count()
|
||||||
|
>= configManager.getMaxDispensers()) {
|
||||||
|
player.sendMessage(configManager.getMessage("max-spender-erreicht")); return false;
|
||||||
|
}
|
||||||
|
} else if (type == Material.DROPPER) {
|
||||||
|
if (connected.stream().filter(l -> getMaterialAt(l) == Material.DROPPER).count()
|
||||||
|
>= configManager.getMaxDroppers()) {
|
||||||
|
player.sendMessage(configManager.getMessage("max-werfer-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) {
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add("§8ID: " + buttonId);
|
||||||
|
lore.add("§7Ein universeller Controller für");
|
||||||
|
meta.setLore(lore);
|
||||||
|
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 isLamp(Material m) {
|
||||||
|
return m == Material.REDSTONE_LAMP
|
||||||
|
|| "COPPER_BULB".equals(m.name())
|
||||||
|
|| m.name().endsWith("_COPPER_BULB");
|
||||||
|
}
|
||||||
|
private boolean isCreakingHeart(Material m) { return "CREAKING_HEART".equals(m.name()); }
|
||||||
|
|
||||||
|
private boolean isInteractableTarget(Material m) {
|
||||||
|
return isDoor(m) || isGate(m) || isTrapdoor(m)
|
||||||
|
|| m == Material.IRON_DOOR || m == Material.IRON_TRAPDOOR
|
||||||
|
|| isLamp(m) || plugin.isGrate(m)
|
||||||
|
|| m == Material.NOTE_BLOCK || m == Material.BELL
|
||||||
|
|| m == Material.DISPENSER || m == Material.DROPPER
|
||||||
|
|| isCreakingHeart(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Material getMaterialAt(String locString) {
|
||||||
Location l = parseLocation(locString);
|
Location l = parseLocation(locString);
|
||||||
return l != null ? l.getBlock().getType() : Material.AIR;
|
return l != null ? l.getBlock().getType() : Material.AIR;
|
||||||
}
|
}
|
||||||
@@ -278,18 +507,8 @@ public class ButtonListener implements Listener {
|
|||||||
String[] p = s.split(",");
|
String[] p = s.split(",");
|
||||||
if (p.length != 4) return null;
|
if (p.length != 4) return null;
|
||||||
try {
|
try {
|
||||||
return new Location(Bukkit.getWorld(p[0]), Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3]));
|
return new Location(Bukkit.getWorld(p[0]),
|
||||||
|
Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3]));
|
||||||
} catch (Exception e) { return null; }
|
} catch (Exception e) { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtonLore(ItemStack item, String buttonId) {
|
|
||||||
ItemMeta meta = item.getItemMeta();
|
|
||||||
if (meta != null) {
|
|
||||||
List<String> lore = new ArrayList<>();
|
|
||||||
lore.add("§8ID: " + buttonId);
|
|
||||||
lore.add("§7Ein universeller Controller für");
|
|
||||||
meta.setLore(lore);
|
|
||||||
item.setItemMeta(meta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,15 @@ import java.util.List;
|
|||||||
|
|
||||||
public class ButtonTabCompleter implements TabCompleter {
|
public class ButtonTabCompleter implements TabCompleter {
|
||||||
|
|
||||||
private final List<String> commands = Arrays.asList("info", "reload", "note", "trust", "untrust", "public", "private");
|
private final List<String> commands = Arrays.asList(
|
||||||
|
"info", "reload", "note", "list", "rename", "schedule",
|
||||||
|
"trust", "untrust", "public", "private", "undo", "secret"
|
||||||
|
);
|
||||||
|
|
||||||
private final List<String> instruments = Arrays.asList(
|
private final List<String> instruments = Arrays.asList(
|
||||||
"PIANO", "BASS_DRUM", "SNARE_DRUM", "STICKS", "BASS_GUITAR",
|
"PIANO", "BASS_DRUM", "SNARE_DRUM", "STICKS", "BASS_GUITAR",
|
||||||
"FLUTE", "BELL", "CHIME", "GUITAR", "XYLOPHONE",
|
"FLUTE", "BELL", "CHIME", "GUITAR", "XYLOPHONE",
|
||||||
"IRON_XYLOPHONE", "COW_BELL", "DIDGERIDOO", "BIT", "BANJO", "PLING"
|
"IRON_XYLOPHONE", "COW_BELL", "DIDGERIDOO", "BIT", "BANJO", "PLING"
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -26,19 +30,41 @@ public class ButtonTabCompleter implements TabCompleter {
|
|||||||
|
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
|
|
||||||
// Erste Ebene: /bc <Tab>
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
StringUtil.copyPartialMatches(args[0], commands, completions);
|
StringUtil.copyPartialMatches(args[0], commands, completions);
|
||||||
}
|
|
||||||
|
} else if (args.length == 2) {
|
||||||
// Zweite Ebene: /bc note <Tab>
|
switch (args[0].toLowerCase()) {
|
||||||
else if (args.length == 2 && args[0].equalsIgnoreCase("note")) {
|
case "note":
|
||||||
StringUtil.copyPartialMatches(args[1], instruments, completions);
|
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;
|
||||||
|
case "secret":
|
||||||
|
StringUtil.copyPartialMatches(args[1], Arrays.asList("select", "info", "add", "remove", "clear", "delay", "animation"), completions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("secret") && args[1].equalsIgnoreCase("delay")) {
|
||||||
|
completions.add("3");
|
||||||
|
completions.add("5");
|
||||||
|
completions.add("10");
|
||||||
|
completions.add("30");
|
||||||
|
completions.add("60");
|
||||||
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("secret") && args[1].equalsIgnoreCase("animation")) {
|
||||||
|
completions.add("instant");
|
||||||
|
completions.add("wave");
|
||||||
|
completions.add("reverse");
|
||||||
|
completions.add("center");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zweite Ebene: /bc trust/untrust <Tab> (Spielernamen vorschlagen)
|
if (args.length == 2 && args[0].equalsIgnoreCase("secret")) {
|
||||||
else if (args.length == 2 && (args[0].equalsIgnoreCase("trust") || args[0].equalsIgnoreCase("untrust"))) {
|
if (completions.isEmpty()) {
|
||||||
return null; // 'null' lässt Bukkit automatisch alle Online-Spieler vorschlagen
|
StringUtil.copyPartialMatches(args[1], Arrays.asList("select", "info", "add", "remove", "clear", "delay", "animation"), completions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(completions);
|
Collections.sort(completions);
|
||||||
|
|||||||
@@ -21,143 +21,192 @@ 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, "max-dispensers", 20);
|
||||||
if (!config.contains("double-note-enabled")) config.set("double-note-enabled", true);
|
def(config, "max-droppers", 20);
|
||||||
if (!config.contains("double-note-delay-ms")) config.set("double-note-delay-ms", 1000);
|
def(config, "default-note", "PIANO");
|
||||||
if (!config.contains("motion-detection-radius")) config.set("motion-detection-radius", 5.0);
|
def(config, "double-note-enabled", true);
|
||||||
if (!config.contains("motion-close-delay-ms")) config.set("motion-close-delay-ms", 5000);
|
def(config, "double-note-delay-ms", 1000);
|
||||||
|
def(config, "motion-detection-radius", 5.0);
|
||||||
|
def(config, "motion-close-delay-ms", 5000);
|
||||||
|
def(config, "motion-trigger-cooldown-ms", 2000);
|
||||||
|
def(config, "timed-container-interval-ticks", 40);
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
// Creaking Heart
|
||||||
if (!lang.contains("bloecke-umgeschaltet")) lang.set("bloecke-umgeschaltet", "§eBlöcke wurden umgeschaltet.");
|
def(lang, "creaking-heart-aktiviert", "§aKnarrherz wurde aktiviert.");
|
||||||
if (!lang.contains("keine-bloecke-verbunden")) lang.set("keine-bloecke-verbunden", "§cKeine Blöcke sind verbunden.");
|
def(lang, "creaking-heart-deaktiviert", "§cKnarrherz wurde deaktiviert.");
|
||||||
if (!lang.contains("block-verbunden")) lang.set("block-verbunden", "§aBlock verbunden.");
|
// Gitter
|
||||||
if (!lang.contains("block-bereits-verbunden")) lang.set("block-bereits-verbunden", "§cBlock ist bereits verbunden.");
|
def(lang, "gitter-geoeffnet", "§aGitter wurden geöffnet.");
|
||||||
if (!lang.contains("controller-platziert")) lang.set("controller-platziert", "§aController platziert.");
|
def(lang, "gitter-geschlossen", "§cGitter wurden geschlossen.");
|
||||||
if (!lang.contains("controller-entfernt")) lang.set("controller-entfernt", "§cController entfernt.");
|
// Glocken
|
||||||
if (!lang.contains("instrument-gesetzt")) lang.set("instrument-gesetzt", "§aDein Notenblock-Instrument wurde auf %s gesetzt.");
|
def(lang, "glocke-gelaeutet", "§aGlocke wurde geläutet.");
|
||||||
if (!lang.contains("ungueltiges-instrument")) lang.set("ungueltiges-instrument", "§cUngültiges Instrument! Verwende: /bc note <Instrument>");
|
def(lang, "max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht.");
|
||||||
if (!lang.contains("konfiguration-reloaded")) lang.set("konfiguration-reloaded", "§aKonfiguration und Daten erfolgreich neu geladen!");
|
// Spender / Werfer
|
||||||
if (!lang.contains("keine-berechtigung")) lang.set("keine-berechtigung", "§cDu hast keine Berechtigung für diesen Befehl!");
|
def(lang, "spender-ausgeloest", "§aSpender wurden ausgelöst.");
|
||||||
if (!lang.contains("kolben-ausgefahren")) lang.set("kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren.");
|
def(lang, "werfer-ausgeloest", "§aWerfer wurden ausgelöst.");
|
||||||
if (!lang.contains("kolben-eingefahren")) lang.set("kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen.");
|
def(lang, "max-spender-erreicht", "§cMaximale Anzahl an Spendern erreicht.");
|
||||||
if (!lang.contains("max-kolben-erreicht")) lang.set("max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht.");
|
def(lang, "max-werfer-erreicht", "§cMaximale Anzahl an Werfern erreicht.");
|
||||||
|
// Notenblöcke
|
||||||
|
def(lang, "notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst.");
|
||||||
|
def(lang, "instrument-gesetzt", "§aDein Instrument wurde auf %s gesetzt.");
|
||||||
|
def(lang, "ungueltiges-instrument", "§cUngültiges Instrument! /bc note <Instrument>");
|
||||||
|
def(lang, "max-notenbloecke-erreicht", "§cMaximale Anzahl an Notenblöcken erreicht.");
|
||||||
|
// Kolben (vorbereitet)
|
||||||
|
def(lang, "kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren.");
|
||||||
|
def(lang, "kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen.");
|
||||||
|
def(lang, "max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht.");
|
||||||
|
// Controller
|
||||||
|
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 int getMaxDispensers() { return config.getInt("max-dispensers", 20); }
|
||||||
|
public int getMaxDroppers() { return config.getInt("max-droppers", 20); }
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ 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;
|
||||||
@@ -13,57 +14,282 @@ 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 String getButtonIdForLocation(String location) {
|
public void shutdown() {
|
||||||
return getButtonIdForPlacedController(location);
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Zugriff & Berechtigungen
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
public boolean canAccess(String buttonId, UUID playerUUID) {
|
public boolean canAccess(String buttonId, UUID playerUUID) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.canAccess(buttonId, playerUUID);
|
||||||
if (isPublic(buttonId)) return true;
|
if (isPublic(buttonId)) return true;
|
||||||
if (isOwner(buttonId, playerUUID)) return true;
|
if (isOwner(buttonId, playerUUID)) return true;
|
||||||
List<String> trusted = data.getStringList("trust." + buttonId);
|
return data.getStringList("trust." + buttonId).contains(playerUUID.toString());
|
||||||
return trusted.contains(playerUUID.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOwner(String buttonId, UUID playerUUID) {
|
public boolean isOwner(String buttonId, UUID playerUUID) {
|
||||||
// Wir prüfen direkt im globalen Pfad der Buttons
|
if (mySQLStorage != null) return mySQLStorage.isOwner(buttonId, playerUUID);
|
||||||
return data.contains("players." + playerUUID.toString() + ".buttons." + buttonId);
|
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) {
|
public void registerController(String location, UUID ownerUUID, String buttonId) {
|
||||||
addPlacedController(ownerUUID.toString(), location, 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) {
|
public void removeController(String location) {
|
||||||
if (data.getConfigurationSection("players") == null) return;
|
if (mySQLStorage != null) {
|
||||||
for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) {
|
mySQLStorage.removeController(location);
|
||||||
String path = "players." + playerUUID + ".placed-controllers." + location;
|
return;
|
||||||
if (data.contains(path)) {
|
}
|
||||||
data.set(path, null);
|
// buttonId vor dem Löschen des Location-Eintrags ermitteln
|
||||||
|
String buttonId = getButtonIdForPlacedController(location);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Alle zugehörigen Daten (Name, Status, Trust, Zeitplan, Verbindungen, Secret) 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);
|
||||||
|
|
||||||
|
// Secret-Wall-Daten ebenfalls entfernen
|
||||||
|
data.set("secret-walls." + buttonId, null);
|
||||||
|
|
||||||
|
// Sicherheitshalber bei ALLEN Spielern den Button-Eintrag löschen
|
||||||
|
if (data.getConfigurationSection("players") != null) {
|
||||||
|
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
|
||||||
|
data.set("players." + uuid + ".buttons." + buttonId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMotionSensorSettings(location);
|
removeMotionSensorSettings(location);
|
||||||
saveData();
|
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) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setConnectedBlocks(playerUUID, buttonId, blocks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("players." + playerUUID + ".buttons." + buttonId, blocks);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getConnectedBlocks(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getConnectedBlocks(buttonId);
|
||||||
|
if (data.getConfigurationSection("players") == null) return new ArrayList<>();
|
||||||
|
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
|
||||||
|
String path = "players." + uuid + ".buttons." + buttonId;
|
||||||
|
if (data.contains(path)) return data.getStringList(path);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleShotDelayTicks(String buttonId, int ticks) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setScheduleShotDelayTicks(buttonId, ticks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("schedules." + buttonId + ".shot-delay-ticks", ticks);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScheduleShotDelayTicks(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getScheduleShotDelayTicks(buttonId);
|
||||||
|
return data.getInt("schedules." + buttonId + ".shot-delay-ticks", -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleTriggerMode(String buttonId, String mode) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setScheduleTriggerMode(buttonId, mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("schedules." + buttonId + ".trigger-mode", mode);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheduleTriggerMode(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getScheduleTriggerMode(buttonId);
|
||||||
|
return data.getString("schedules." + buttonId + ".trigger-mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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) {
|
public void addTrustedPlayer(String buttonId, UUID targetUUID) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.addTrustedPlayer(buttonId, targetUUID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<String> trusted = data.getStringList("trust." + buttonId);
|
List<String> trusted = data.getStringList("trust." + buttonId);
|
||||||
if (!trusted.contains(targetUUID.toString())) {
|
if (!trusted.contains(targetUUID.toString())) {
|
||||||
trusted.add(targetUUID.toString());
|
trusted.add(targetUUID.toString());
|
||||||
@@ -73,6 +299,10 @@ public class DataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeTrustedPlayer(String buttonId, UUID targetUUID) {
|
public void removeTrustedPlayer(String buttonId, UUID targetUUID) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.removeTrustedPlayer(buttonId, targetUUID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<String> trusted = data.getStringList("trust." + buttonId);
|
List<String> trusted = data.getStringList("trust." + buttonId);
|
||||||
trusted.remove(targetUUID.toString());
|
trusted.remove(targetUUID.toString());
|
||||||
data.set("trust." + buttonId, trusted);
|
data.set("trust." + buttonId, trusted);
|
||||||
@@ -80,96 +310,156 @@ public class DataManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPublic(String buttonId, boolean isPublic) {
|
public void setPublic(String buttonId, boolean isPublic) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setPublic(buttonId, isPublic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
data.set("public-status." + buttonId, isPublic);
|
data.set("public-status." + buttonId, isPublic);
|
||||||
saveData();
|
saveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPublic(String buttonId) {
|
public boolean isPublic(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.isPublic(buttonId);
|
||||||
return data.getBoolean("public-status." + buttonId, false);
|
return data.getBoolean("public-status." + buttonId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichert die Blöcke für eine ID unter einem spezifischen Spieler
|
// -----------------------------------------------------------------------
|
||||||
public void setConnectedBlocks(String playerUUID, String buttonId, List<String> blocks) {
|
// Instrumente
|
||||||
data.set("players." + playerUUID + ".buttons." + buttonId, blocks);
|
// -----------------------------------------------------------------------
|
||||||
saveData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPlacedController(String playerUUID, String location, String buttonId) {
|
|
||||||
// Verhindert doppelte Punkte im Pfad, falls die Location Punkte enthält
|
|
||||||
data.set("players." + playerUUID + ".placed-controllers." + location, buttonId);
|
|
||||||
saveData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getButtonIdForPlacedController(String location) {
|
|
||||||
if (data.getConfigurationSection("players") == null) return null;
|
|
||||||
for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) {
|
|
||||||
String buttonId = data.getString("players." + playerUUID + ".placed-controllers." + location);
|
|
||||||
if (buttonId != null) return buttonId;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VERBESSERT: Sucht gezielt nach den Blöcken für diese ID
|
|
||||||
public List<String> getConnectedBlocks(String buttonId) {
|
|
||||||
if (data.getConfigurationSection("players") == null) return new ArrayList<>();
|
|
||||||
|
|
||||||
for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) {
|
|
||||||
String path = "players." + playerUUID + ".buttons." + buttonId;
|
|
||||||
if (data.contains(path)) {
|
|
||||||
return data.getStringList(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ArrayList<>(); // Niemals null zurückgeben, um Fehler im Listener zu vermeiden
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getAllPlacedControllers() {
|
|
||||||
List<String> allControllers = new ArrayList<>();
|
|
||||||
if (data.getConfigurationSection("players") == null) return allControllers;
|
|
||||||
for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) {
|
|
||||||
if (data.getConfigurationSection("players." + playerUUID + ".placed-controllers") != null) {
|
|
||||||
allControllers.addAll(data.getConfigurationSection("players." + playerUUID + ".placed-controllers").getKeys(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allControllers;
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveData() {
|
// -----------------------------------------------------------------------
|
||||||
try {
|
// Secret-Wall (Geheimwand)
|
||||||
data.save(dataFile);
|
// -----------------------------------------------------------------------
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().severe("Konnte data.yml nicht speichern: " + e.getMessage());
|
public void setSecretBlocks(String buttonId, List<String> blocks) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretBlocks(buttonId, blocks);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".blocks", blocks);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSecretBlocks(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretBlocks(buttonId);
|
||||||
|
return data.getStringList("secret-walls." + buttonId + ".blocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretRestoreDelayMs(String buttonId, long delayMs) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretRestoreDelayMs(buttonId, delayMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".delay-ms", delayMs);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSecretRestoreDelayMs(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretRestoreDelayMs(buttonId);
|
||||||
|
return data.getLong("secret-walls." + buttonId + ".delay-ms", 5000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretAnimation(String buttonId, String animation) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretAnimation(buttonId, animation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".animation", animation);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecretAnimation(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretAnimation(buttonId);
|
||||||
|
return data.getString("secret-walls." + buttonId + ".animation", "wave");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSecret(String buttonId) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.clearSecret(buttonId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId, null);
|
||||||
|
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() {
|
||||||
|
if (mySQLStorage != null) return;
|
||||||
|
final String serialized;
|
||||||
|
try {
|
||||||
|
serialized = data.saveToString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,22 @@ package viper;
|
|||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
// Import aus dem korrekten verschobenen Package:
|
// Import aus dem korrekten verschobenen Package:
|
||||||
import org.bstats.bukkit.Metrics;
|
|
||||||
|
|
||||||
|
|
||||||
public class MetricsHandler {
|
public class MetricsHandler {
|
||||||
|
|
||||||
private static final int BSTATS_PLUGIN_ID = 26862;
|
private static final int BSTATS_PLUGIN_ID = 26862;
|
||||||
|
|
||||||
public static void startMetrics(JavaPlugin plugin) {
|
public static void startMetrics(JavaPlugin plugin) {
|
||||||
new Metrics(plugin, BSTATS_PLUGIN_ID);
|
try {
|
||||||
|
Class<?> metricsClass = Class.forName("org.bstats.bukkit.Metrics");
|
||||||
|
metricsClass.getConstructor(JavaPlugin.class, int.class)
|
||||||
|
.newInstance(plugin, BSTATS_PLUGIN_ID);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
plugin.getLogger().info("bStats nicht gefunden – Telemetrie deaktiviert.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("bStats konnte nicht initialisiert werden: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
680
src/main/java/viper/MySQLStorage.java
Normal file
680
src/main/java/viper/MySQLStorage.java
Normal file
@@ -0,0 +1,680 @@
|
|||||||
|
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 Verbindung fehlgeschlagen - verwende data.yml für Datenspeicherung.");
|
||||||
|
plugin.getLogger().fine("Fehlerdetails: " + 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,"
|
||||||
|
+ "shot_delay_ticks INT,"
|
||||||
|
+ "trigger_mode VARCHAR(16)"
|
||||||
|
+ ")");
|
||||||
|
|
||||||
|
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"
|
||||||
|
+ ")");
|
||||||
|
|
||||||
|
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_secret_walls ("
|
||||||
|
+ "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_secret_settings ("
|
||||||
|
+ "button_id VARCHAR(64) PRIMARY KEY,"
|
||||||
|
+ "delay_ms BIGINT NOT NULL,"
|
||||||
|
+ "animation VARCHAR(16) NOT NULL DEFAULT 'wave'"
|
||||||
|
+ ")");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_schedules "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS shot_delay_ticks INT");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_schedules "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS trigger_mode VARCHAR(16)");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_secret_settings "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS animation VARCHAR(16) NOT NULL DEFAULT 'wave'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ?",
|
||||||
|
"DELETE FROM bc_secret_walls WHERE button_id = ?",
|
||||||
|
"DELETE FROM bc_secret_settings 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, shot_delay_ticks, trigger_mode) VALUES (?, ?, -1, -1, 'simultaneous')"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE open_time = VALUES(open_time)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setLong(2, ticks);
|
||||||
|
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, shot_delay_ticks, trigger_mode) VALUES (?, -1, ?, -1, 'simultaneous')"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE close_time = VALUES(close_time)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setLong(2, 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 setScheduleShotDelayTicks(String buttonId, int ticks) {
|
||||||
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, -1, -1, ?, 'simultaneous')"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE shot_delay_ticks = VALUES(shot_delay_ticks)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setInt(2, ticks);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setScheduleShotDelayTicks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScheduleShotDelayTicks(String buttonId) {
|
||||||
|
String q = "SELECT shot_delay_ticks FROM bc_schedules WHERE button_id = ? LIMIT 1";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (!rs.next()) return -1;
|
||||||
|
int delay = rs.getInt(1);
|
||||||
|
return rs.wasNull() ? -1 : delay;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getScheduleShotDelayTicks Fehler: " + e.getMessage());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleTriggerMode(String buttonId, String mode) {
|
||||||
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, -1, -1, -1, ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE trigger_mode = VALUES(trigger_mode)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setString(2, mode);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setScheduleTriggerMode Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheduleTriggerMode(String buttonId) {
|
||||||
|
String q = "SELECT trigger_mode 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.getString(1) : null;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getScheduleTriggerMode Fehler: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretBlocks(String buttonId, List<String> blocks) {
|
||||||
|
String del = "DELETE FROM bc_secret_walls WHERE button_id = ?";
|
||||||
|
String ins = "INSERT INTO bc_secret_walls (button_id, block_location) VALUES (?, ?)";
|
||||||
|
try (PreparedStatement psDel = getConnection().prepareStatement(del);
|
||||||
|
PreparedStatement psIns = getConnection().prepareStatement(ins)) {
|
||||||
|
psDel.setString(1, buttonId);
|
||||||
|
psDel.executeUpdate();
|
||||||
|
|
||||||
|
for (String block : blocks) {
|
||||||
|
psIns.setString(1, buttonId);
|
||||||
|
psIns.setString(2, block);
|
||||||
|
psIns.addBatch();
|
||||||
|
}
|
||||||
|
psIns.executeBatch();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretBlocks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSecretBlocks(String buttonId) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
String q = "SELECT block_location FROM bc_secret_walls 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 getSecretBlocks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretRestoreDelayMs(String buttonId, long delayMs) {
|
||||||
|
String q = "INSERT INTO bc_secret_settings (button_id, delay_ms) VALUES (?, ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE delay_ms = VALUES(delay_ms)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setLong(2, delayMs);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretRestoreDelayMs Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSecretRestoreDelayMs(String buttonId) {
|
||||||
|
String q = "SELECT delay_ms FROM bc_secret_settings 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) : 5000L;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getSecretRestoreDelayMs Fehler: " + e.getMessage());
|
||||||
|
return 5000L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretAnimation(String buttonId, String animation) {
|
||||||
|
String q = "INSERT INTO bc_secret_settings (button_id, delay_ms, animation) VALUES (?, COALESCE((SELECT delay_ms FROM bc_secret_settings WHERE button_id = ?), 5000), ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE animation = VALUES(animation)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setString(2, buttonId);
|
||||||
|
ps.setString(3, animation);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretAnimation Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecretAnimation(String buttonId) {
|
||||||
|
String q = "SELECT animation FROM bc_secret_settings 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) : "wave";
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getSecretAnimation Fehler: " + e.getMessage());
|
||||||
|
return "wave";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSecret(String buttonId) {
|
||||||
|
String q1 = "DELETE FROM bc_secret_walls WHERE button_id = ?";
|
||||||
|
String q2 = "DELETE FROM bc_secret_settings WHERE button_id = ?";
|
||||||
|
try (PreparedStatement ps1 = getConnection().prepareStatement(q1);
|
||||||
|
PreparedStatement ps2 = getConnection().prepareStatement(q2)) {
|
||||||
|
ps1.setString(1, buttonId);
|
||||||
|
ps1.executeUpdate();
|
||||||
|
ps2.setString(1, buttonId);
|
||||||
|
ps2.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL clearSecret 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
315
src/main/java/viper/ScheduleGUI.java
Normal file
315
src/main/java/viper/ScheduleGUI.java
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
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;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI zur Konfiguration der zeitgesteuerten Automatisierung eines Controllers.
|
||||||
|
*
|
||||||
|
* Layout (9×3 = 27 Slots):
|
||||||
|
* Slot 4 – Abschuss-Verzögerung Werfer/Spender (REPEATER) ← Links/Rechts: ±1 Tick | Shift: ±5
|
||||||
|
* Slot 6 – Schuss-Modus (COMPARATOR) ← Klick: gleichzeitig / nacheinander
|
||||||
|
* 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 (0–23999).
|
||||||
|
* 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 int shotDelayTicks;
|
||||||
|
private String triggerMode;
|
||||||
|
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);
|
||||||
|
int savedShotDelay = dataManager.getScheduleShotDelayTicks(buttonId);
|
||||||
|
String savedTriggerMode = dataManager.getScheduleTriggerMode(buttonId);
|
||||||
|
this.openTime = savedOpen >= 0 ? savedOpen : plugin.timeToTicks(7, 0); // 07:00
|
||||||
|
this.closeTime = savedClose >= 0 ? savedClose : plugin.timeToTicks(19, 0); // 19:00
|
||||||
|
this.shotDelayTicks = savedShotDelay >= 0
|
||||||
|
? savedShotDelay
|
||||||
|
: Math.max(1, plugin.getConfigManager().getConfig().getInt("timed-container-shot-delay-ticks", 2));
|
||||||
|
this.triggerMode = normalizeTriggerMode(savedTriggerMode != null
|
||||||
|
? savedTriggerMode
|
||||||
|
: plugin.getConfigManager().getConfig().getString("timed-container-trigger-mode", "simultaneous"));
|
||||||
|
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 4 – Abschuss-Verzögerung
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
|
||||||
|
// Slot 6 – Modus
|
||||||
|
inv.setItem(6, makeModeItem());
|
||||||
|
|
||||||
|
// Slot 10 – Öffnungszeit
|
||||||
|
inv.setItem(10, makeTimeItem(
|
||||||
|
Material.LIME_DYE,
|
||||||
|
"§a§lÖffnungszeit",
|
||||||
|
openTime,
|
||||||
|
"§7Linksklick: §f+1 Stunde",
|
||||||
|
"§7Rechtsklick: §f−1 Stunde",
|
||||||
|
"§7Shift+Links: §f+15 Minuten",
|
||||||
|
"§7Shift+Rechts: §f−15 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: §f−1 Stunde",
|
||||||
|
"§7Shift+Links: §f+15 Minuten",
|
||||||
|
"§7Shift+Rechts: §f−15 Minuten"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Slot 22 – Speichern
|
||||||
|
inv.setItem(22, makeItem(Material.EMERALD,
|
||||||
|
"§a§lSpeichern & Schließen",
|
||||||
|
"§7Speichert den aktuellen Zeitplan."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack makeDelayItem() {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add("§e§l" + shotDelayTicks + " Ticks §7(" + formatShotDelaySeconds() + "s§7)");
|
||||||
|
if (isSequentialMode()) {
|
||||||
|
lore.add("§7Aktuell: §f" + shotDelayTicks + " Ticks zwischen einzelnen Geräten");
|
||||||
|
} else if (shotDelayTicks <= 1) {
|
||||||
|
lore.add("§7Aktuell: §falle verbundenen Werfer schießen jeden Tick");
|
||||||
|
} else {
|
||||||
|
lore.add("§7Aktuell: §f" + shotDelayTicks + " Ticks zwischen gemeinsamen Schüssen");
|
||||||
|
}
|
||||||
|
lore.add("");
|
||||||
|
lore.add("§7Linksklick: §f+1 Tick");
|
||||||
|
lore.add("§7Rechtsklick: §f−1 Tick");
|
||||||
|
lore.add("§7Shift+Links: §f+5 Ticks");
|
||||||
|
lore.add("§7Shift+Rechts: §f−5 Ticks");
|
||||||
|
lore.add("§8(1 Tick = schnellstmöglich)");
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(Material.REPEATER);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName("§b§lAbschuss-Verzögerung");
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack makeModeItem() {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(isSequentialMode()
|
||||||
|
? "§e§lNacheinander"
|
||||||
|
: "§e§lGleichzeitig");
|
||||||
|
lore.add("");
|
||||||
|
lore.add("§7Klick: §fModus wechseln");
|
||||||
|
lore.add("§8Gleichzeitig = alle Werfer zusammen");
|
||||||
|
lore.add("§8Nacheinander = Geräte rotieren der Reihe nach");
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(Material.COMPARATOR);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName("§d§lSchuss-Modus");
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatShotDelaySeconds() {
|
||||||
|
return String.format(Locale.US, "%.2f", shotDelayTicks / 20.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSequentialMode() {
|
||||||
|
return "sequential".equals(triggerMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeTriggerMode(String mode) {
|
||||||
|
return "sequential".equalsIgnoreCase(mode) ? "sequential" : "simultaneous";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (0–26) 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 == 4) {
|
||||||
|
int delayStep = event.isShiftClick() ? 5 : 1;
|
||||||
|
if (event.isLeftClick()) shotDelayTicks += delayStep;
|
||||||
|
if (event.isRightClick()) shotDelayTicks -= delayStep;
|
||||||
|
if (shotDelayTicks < 1) shotDelayTicks = 1;
|
||||||
|
if (shotDelayTicks > 200) shotDelayTicks = 200;
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
|
||||||
|
} else if (slot == 6) {
|
||||||
|
triggerMode = isSequentialMode() ? "simultaneous" : "sequential";
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
inv.setItem(6, makeModeItem());
|
||||||
|
|
||||||
|
} else 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: §f−1 Stunde",
|
||||||
|
"§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f−15 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: §f−1 Stunde",
|
||||||
|
"§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f−15 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);
|
||||||
|
dataManager.setScheduleShotDelayTicks(buttonId, shotDelayTicks);
|
||||||
|
dataManager.setScheduleTriggerMode(buttonId, triggerMode);
|
||||||
|
player.sendMessage("§a[BC] §7Zeitplan gespeichert: §aÖffnet §7um §e"
|
||||||
|
+ plugin.ticksToTime(openTime)
|
||||||
|
+ " §7· §cSchließt §7um §e"
|
||||||
|
+ plugin.ticksToTime(closeTime)
|
||||||
|
+ " §7· §bDelay §e"
|
||||||
|
+ shotDelayTicks
|
||||||
|
+ "§7 Ticks §8("
|
||||||
|
+ formatShotDelaySeconds()
|
||||||
|
+ "s§8) §7· §dModus §e"
|
||||||
|
+ (isSequentialMode() ? "nacheinander" : "gleichzeitig"));
|
||||||
|
} else {
|
||||||
|
dataManager.clearSchedule(buttonId);
|
||||||
|
player.sendMessage("§7[BC] Zeitplan deaktiviert.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,63 @@
|
|||||||
# 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
|
||||||
max-gates: 20
|
max-gates: 20
|
||||||
max-trapdoors: 20
|
max-trapdoors: 20
|
||||||
max-bells: 5
|
max-bells: 5
|
||||||
|
max-dispensers: 20
|
||||||
|
max-droppers: 20
|
||||||
|
|
||||||
# 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
|
||||||
|
# Legacy-Fallback für ältere Zeitpläne ohne eigenen GUI-Delay-Wert
|
||||||
|
# (20 Ticks = 1 Sekunde)
|
||||||
|
timed-container-interval-ticks: 40
|
||||||
|
# Standardwert für die Zeit zwischen einzelnen Abschüssen im Zeitplan
|
||||||
|
# Wird verwendet, bis ein Controller in der GUI einen eigenen Wert speichert
|
||||||
|
timed-container-shot-delay-ticks: 2
|
||||||
|
# Standardmodus für Zeitplan-Werfer/Spender: simultaneous oder sequential
|
||||||
|
timed-container-trigger-mode: simultaneous
|
||||||
|
|
||||||
|
# ── 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
|
||||||
@@ -1,57 +1,72 @@
|
|||||||
# lang.yml - ButtonControl Nachrichten
|
# lang.yml – ButtonControl Nachrichten
|
||||||
|
|
||||||
# --- Bestehende Nachrichten (Türen/Tore/Falltüren) ---
|
# ── 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."
|
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 & Glocken ─────────────────────────────────────────────────────────
|
||||||
lampen-eingeschaltet: "§aLampen wurden eingeschaltet."
|
lampen-eingeschaltet: "§aLampen wurden eingeschaltet."
|
||||||
lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet."
|
lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet."
|
||||||
max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht."
|
max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht."
|
||||||
|
creaking-heart-aktiviert: "§aKnarrherz wurde aktiviert."
|
||||||
|
creaking-heart-deaktiviert: "§cKnarrherz wurde deaktiviert."
|
||||||
|
gitter-geoeffnet: "§aGitter wurden geöffnet."
|
||||||
|
gitter-geschlossen: "§cGitter wurden geschlossen."
|
||||||
glocke-gelaeutet: "§aGlocke wurde geläutet."
|
glocke-gelaeutet: "§aGlocke wurde geläutet."
|
||||||
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht."
|
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht."
|
||||||
|
spender-ausgeloest: "§aSpender wurden ausgelöst."
|
||||||
|
werfer-ausgeloest: "§aWerfer wurden ausgelöst."
|
||||||
|
max-spender-erreicht: "§cMaximale Anzahl an Spendern erreicht."
|
||||||
|
max-werfer-erreicht: "§cMaximale Anzahl an Werfern erreicht."
|
||||||
|
|
||||||
# --- Notenblöcke & Instrumente ---
|
# ── Notenblöcke ──────────────────────────────────────────────────────────────
|
||||||
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst."
|
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst."
|
||||||
instrument-gesetzt: "§aDein Notenblock-Instrument wurde auf %s gesetzt."
|
instrument-gesetzt: "§aDein Instrument wurde auf %s gesetzt."
|
||||||
ungueltiges-instrument: "§cUngültiges Instrument! Verwende: /bc note <Instrument>"
|
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."
|
||||||
|
|
||||||
# --- Kolben (Erweiterung) ---
|
# ── Kolben (vorbereitet für zukünftige Erweiterung) ─────────────────────────
|
||||||
kolben-ausgefahren: "§6[ButtonControl] §7Kolben wurden ausgefahren."
|
kolben-ausgefahren: "§6[ButtonControl] §7Kolben wurden ausgefahren."
|
||||||
kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingefahren."
|
kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingezogen."
|
||||||
max-kolben-erreicht: "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."
|
max-kolben-erreicht: "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."
|
||||||
|
|
||||||
# --- Controller & Verbindung ---
|
# ── 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."
|
keine-bloecke-verbunden: "§cKeine Blöcke sind verbunden."
|
||||||
bloecke-umgeschaltet: "§eBlöcke wurden umgeschaltet."
|
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"
|
||||||
|
|
||||||
# --- Trust- & Sicherheitssystem ---
|
# ── Trust- & Sicherheitssystem ───────────────────────────────────────────────
|
||||||
keine-berechtigung: "§cDu hast keine Berechtigung für diesen Befehl!"
|
keine-berechtigung: "§cDu hast keine Berechtigung für diesen Befehl!"
|
||||||
keine-berechtigung-controller: "§cDu hast keine Berechtigung, diesen Controller zu benutzen!"
|
keine-berechtigung-controller: "§cDu darfst diesen Controller nicht benutzen!"
|
||||||
nur-besitzer-abbauen: "§cNur der Besitzer darf diesen Controller verwalten oder abbauen!"
|
nur-besitzer-abbauen: "§cNur der Besitzer kann diesen Controller verwalten!"
|
||||||
spieler-nicht-gefunden: "§cDieser Spieler wurde nicht gefunden."
|
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!"
|
||||||
|
|
||||||
# %s wird durch "§aÖffentlich" oder "§cPrivat" ersetzt
|
# ── System ───────────────────────────────────────────────────────────────────
|
||||||
status-geandert: "§6[ButtonControl] §7Der Controller ist nun %s§7."
|
|
||||||
|
|
||||||
# Trust Nachrichten
|
|
||||||
trust-hinzugefuegt: "§aDu hast %s Vertrauen für diesen Controller gegeben."
|
|
||||||
trust-entfernt: "§cDu hast %s das Vertrauen für diesen Controller entzogen."
|
|
||||||
kein-controller-im-blick: "§cDu musst einen Controller (Button, Haken oder Sensor) direkt ansehen!"
|
|
||||||
|
|
||||||
# --- System ---
|
|
||||||
konfiguration-neugeladen: "§aKonfiguration und Daten erfolgreich neu geladen!"
|
konfiguration-neugeladen: "§aKonfiguration und Daten erfolgreich neu geladen!"
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
name: ButtonControl
|
name: ButtonControl
|
||||||
version: 1.5
|
version: 1.9
|
||||||
main: viper.ButtonControl
|
main: viper.ButtonControl
|
||||||
api-version: 1.21
|
api-version: 1.21
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
description: Ein Plugin, um Türen, Redstone-Lampen und Notenblöcke mit einem Button, Tageslichtsensor oder Bewegungsmelder 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: Hauptbefehl für ButtonControl
|
description: Hauptbefehl für ButtonControl
|
||||||
usage: /bc <info|reload|note|trust|untrust|public|private>
|
usage: "/bc <info|reload|note|list|rename|schedule|trust|untrust|public|private|undo|secret> (Secret: select|info|add|remove|clear|delay|animation)"
|
||||||
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
|
default: true
|
||||||
buttoncontrol.update:
|
buttoncontrol.update:
|
||||||
description: Erlaubt das Empfangen von Update-Benachrichtigungen
|
description: Update-Benachrichtigungen empfangen
|
||||||
default: op
|
default: op
|
||||||
buttoncontrol.trust:
|
buttoncontrol.trust:
|
||||||
description: Erlaubt das Verwalten von Vertrauen und Status (Public/Private)
|
description: Trust-System und Public/Private-Status verwalten
|
||||||
default: true
|
default: true
|
||||||
Reference in New Issue
Block a user