Compare commits
7 Commits
77ae1d6ed9
...
main
Author | SHA1 | Date | |
---|---|---|---|
39cf666bf4 | |||
24c551fb5c | |||
790980a5ac | |||
779f031fdc | |||
7fb9afa9d3 | |||
a69200ee17 | |||
73fed5acc5 |
3
pom.xml
3
pom.xml
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
<groupId>de.viper</groupId>
|
<groupId>de.viper</groupId>
|
||||||
<artifactId>SurvivalPlus</artifactId>
|
<artifactId>SurvivalPlus</artifactId>
|
||||||
<version>1.0.8-Beta</version>
|
<version>1.0.9-Beta</version>
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>SurvivalPlus</name>
|
<name>SurvivalPlus</name>
|
||||||
|
189
readme.md
189
readme.md
@@ -1,87 +1,146 @@
|
|||||||
# SurvivalPlus
|
# SurvivalPlus
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
**SurvivalPlus** ist ein Minecraft-Plugin zur Verbesserung des Survival-Erlebnisses.
|
**SurvivalPlus** ist ein Minecraft-Plugin zur Verbesserung des Survival-Erlebnisses.
|
||||||
Es bietet Homes, Teleportation, Inventarverwaltung, Freundeslisten, Shops, Loot-Kisten und weitere Komfortbefehle.
|
Es bietet Homes, Teleports, Inventar-/Enderchest-Verwaltung, Claims (Anti-Grief), Freundeslisten, Shop-System, Loot-Kisten, Tablist-Anpassungen und viele Komfort-Utilities.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚡ Features
|
## ⚡ Highlights / Features
|
||||||
|
|
||||||
- **Homes & Warps** – Homepunkte setzen, löschen, Teleportation, persönliche Warps
|
- Homes & Warps (persönliche Warps, Home-Management)
|
||||||
- **Teleportation & Spielerinteraktion** – `/tp`, `/tphere`, `/tpa`, `/back`, `/spawn`
|
- Teleportation & Anfrage-System (`/tpa`, `/tpaccept`, `/tpdeny`)
|
||||||
- **Inventar & Endertruhe** – Öffnen eigener und fremder Inventare/Endertruhen
|
- Claim-System zur Land-Sicherung (trust/untrust, create/delete, info, list)
|
||||||
- **Spielmodus & Zeit** – `/gm`, `/day`, `/night`
|
- Inventar- & Enderchest-Verwaltung (öffnen von Fremd-Inventaren, Admin-Tools)
|
||||||
- **Freundes- und Kommunikationssystem** – `/friend`, `/block`, `/blocklist`
|
- Shop-System & Lootchests (Verwaltung, Teleport zu Lootchests)
|
||||||
- **Koordinaten teilen** – `/sp share`, `/sp shareconfirm`, `/sp sharecancel`
|
- CommandBlocker & Server-Utilities (clearchat, clearitems, closedoors, lock)
|
||||||
- **Items & Werkzeuge** – `/ir`, `/workbench`, `/anvil`, `/kit`
|
- Debug-Logging (optionale `debug-logging` in config, Debug/debug.log & Debug/console.log)
|
||||||
- **Server-Management** – `/clearchat`, `/clearitems`, `/closedoors`, `/lock`, `/shop`
|
- Tablist (animiert / konfigurierbar über `tablist.yml`)
|
||||||
- **Statistiken & Reporting** – `/stats`, `/report`, `/showreport`, `/clearreport`
|
- bStats Unterstützung (Statistiken)
|
||||||
- **Fun & Challenges** – `/sit`, `/startchallenge`, `/trade`, `/lootchests`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠 Commands Übersicht
|
## 🛠 Komplette Befehlsübersicht
|
||||||
|
|
||||||
|
> Hinweis: In `plugin.yml` sind alle Commands definiert — hier die Übersicht gruppiert nach Kategorie.
|
||||||
|
|
||||||
|
### Allgemein / Haupt
|
||||||
|
| Befehl | Nutzung | Permission |
|
||||||
|
|---|---:|:---|
|
||||||
|
| `/sp` | Hauptbefehl (CommandBlocker, reload, info, share, help u.v.m.) | `survivalplus.sp` |
|
||||||
|
| `/help` (alias über `/sp help`) | Hilfe / Übersicht | `survivalplus.sp` |
|
||||||
|
|
||||||
|
### Item & Utility
|
||||||
|
| Befehl | Usage | Permission |
|
||||||
|
|---|---:|:---|
|
||||||
|
| `/ir <neuer_name>` | Item umbenennen (Item in Hand) | `survivalplus.itemrename` |
|
||||||
|
| `/workbench` | Öffnet Werkbank-GUI | `survivalplus.workbench` |
|
||||||
|
| `/anvil` | Öffnet Amboss-GUI | `survivalplus.anvil` |
|
||||||
|
| `/trash` | Öffnet Mülleimer | `survivalplus.trash` |
|
||||||
|
| `/showarmorstands` | Alle (debug) ArmorStands sichtbar machen | `survivalplus.showarmorstands` |
|
||||||
|
| `/cleardebugarmorstands` | Entfernt Debug-ArmorStands | `survivalplus.cleardebugarmorstands` |
|
||||||
|
| `/leashcount` | Anzahl geleinter Tiere anzeigen | `survivalplus.leashcount` |
|
||||||
|
| `/nick <Name>` | Nickname ändern (Farben/Hex möglich) | `survivalplus.nick` |
|
||||||
|
|
||||||
### Teleportation
|
### Teleportation
|
||||||
| Befehl | Nutzung | Permission |
|
| Befehl | Usage | Permission |
|
||||||
|--------|---------|------------|
|
|---|---:|:---|
|
||||||
| `/tp` | Teleport zu Spieler | `survivalplus.tp` |
|
| `/tp <Spieler>` | Teleport zu Spieler | `survivalplus.tp` |
|
||||||
| `/tphere` | Spieler zu dir teleportieren | `survivalplus.tphere` |
|
| `/tphere <Spieler>` | Teleportiert Spieler zu dir | `survivalplus.tphere` |
|
||||||
| `/tpa` | Teleportanfrage senden | `survivalplus.tpa` |
|
| `/tpa <Spieler>` | Teleportanfrage senden | `survivalplus.tpa` |
|
||||||
| `/tpaccept` | Teleportanfrage annehmen | `survivalplus.tpaccept` |
|
| `/tpaccept` | Teleportanfrage annehmen | `survivalplus.tpaccept` |
|
||||||
| `/tpdeny` | Teleportanfrage ablehnen | `survivalplus.tpdeny` |
|
| `/tpdeny` | Teleportanfrage ablehnen | `survivalplus.tpdeny` |
|
||||||
| `/back` | Zum letzten Todespunkt | `survivalplus.back` |
|
| `/back` | Zum letzten Todespunkt teleportieren | `survivalplus.back` |
|
||||||
| `/spawn` | Weltspawn teleport | `survivalplus.spawn` |
|
| `/spawn` | Teleport zum Weltspawn | `survivalplus.spawn` |
|
||||||
|
| `/setspawn` | Setzt Server-Spawn | `survivalplus.setspawn` |
|
||||||
|
| `/setworldspawn` | Setzt Weltspawn (aktuelle Welt) | `survivalplus.setworldspawn` |
|
||||||
|
|
||||||
### Homes & Warps
|
### Homes & Warps
|
||||||
| Befehl | Nutzung | Permission |
|
| Befehl | Usage | Permission |
|
||||||
|--------|---------|------------|
|
|---|---:|:---|
|
||||||
| `/sethome <name>` | Home setzen | `survivalplus.homes.set` |
|
| `/sethome <name>` | Setzt Home | `survivalplus.homes.set` |
|
||||||
| `/delhome <name>` | Home löschen | `survivalplus.homes.delete` |
|
| `/delhome <name>` | Löscht Home | `survivalplus.homes.delete` |
|
||||||
| `/home <name>` | Teleport zu Home | `survivalplus.homes` |
|
| `/home <name>` | Teleport zu Home | `survivalplus.homes` |
|
||||||
| `/homelist` | Liste aller Homes | `survivalplus.homes.list` |
|
| `/homelist` | GUI mit Homes öffnen | `survivalplus.homes.list` |
|
||||||
| `/setwarp <name>` | Persönlichen Warp setzen | `survivalplus.setwarp` |
|
| `/setwarp <name>` | Persönlichen Warp setzen (Item in Hand) | `survivalplus.setwarp` |
|
||||||
| `/delwarp <name>` | Persönlichen Warp löschen | `survivalplus.delwarp` |
|
| `/delwarp <name>` | Persönlichen Warp löschen | `survivalplus.delwarp` |
|
||||||
| `/warps` | GUI aller Warps öffnen | `survivalplus.warps` |
|
| `/warps` | GUI aller Warps öffnen | `survivalplus.warps` |
|
||||||
|
|
||||||
### Spieler & Kommunikation
|
### Claim (Anti-Grief)
|
||||||
| Befehl | Nutzung | Permission |
|
| Befehl | Usage | Permission |
|
||||||
|--------|---------|------------|
|
|---|---:|:---|
|
||||||
| `/friend` | Freundesliste verwalten | `survivalplus.friend` |
|
| `/claim [unclaim / trust <player> / untrust <player>]` | Claim-Management (Trust/Untrust/Unclaim) | `survivalplus.claim.use` |
|
||||||
| `/block` | Spieler blockieren | `survivalplus.block` |
|
| `/claim create <name>` (falls implementiert) | Neues Claim anlegen | `survivalplus.claim.use` |
|
||||||
| `/unblock` | Blockierung aufheben | `survivalplus.unlock` |
|
| `/claim delete <name>` (falls implementiert) | Claim löschen | `survivalplus.claim.use` |
|
||||||
| `/blocklist` | Liste blockierter Spieler | `survivalplus.blocklist` |
|
| `/claim addmember <player>` | Spieler zum Claim hinzufügen | `survivalplus.claim.trust` |
|
||||||
|
| `/claim removemember <player>` | Spieler entfernen | `survivalplus.claim.trust` |
|
||||||
|
| `/claim info` | Claim-Informationen anzeigen | `survivalplus.claim.use` |
|
||||||
|
| `/claim list` | Eigene Claims auflisten | `survivalplus.claim.use` |
|
||||||
|
|
||||||
### Items & Werkzeuge
|
### CommandBlocker / Server-Management
|
||||||
| Befehl | Nutzung | Permission |
|
| Befehl | Usage | Permission |
|
||||||
|--------|---------|------------|
|
|---|---:|:---|
|
||||||
| `/ir <neuer_name>` | Item umbenennen | `survivalplus.itemrename` |
|
| `/sp cb add <cmd>` | Befehl zur Blockliste hinzufügen | `survivalplus.commandblocker.add` |
|
||||||
| `/workbench` | Werkbank GUI öffnen | `survivalplus.workbench` |
|
| `/sp cb remove <cmd>` | Befehl entfernen | `survivalplus.commandblocker.remove` |
|
||||||
| `/anvil` | Amboss GUI öffnen | `survivalplus.anvil` |
|
| `/sp cb list` | Blockierte Befehle anzeigen | `survivalplus.commandblocker.list` |
|
||||||
| `/kit` | Starterkit erhalten | `survivalplus.kit` |
|
|
||||||
| `/leashcount` | Anzahl geleinter Tiere | `survivalplus.leashcount` |
|
|
||||||
| `/nick <Name>` | Nickname ändern | `survivalplus.nick` |
|
|
||||||
|
|
||||||
### Server-Management
|
|
||||||
| Befehl | Nutzung | Permission |
|
|
||||||
|--------|---------|------------|
|
|
||||||
| `/clearchat` | Chat löschen | `survivalplus.clearchat` |
|
| `/clearchat` | Chat löschen | `survivalplus.clearchat` |
|
||||||
| `/clearitems` | Items entfernen | `survivalplus.clearitems` |
|
| `/clearitems` | Items aufsammeln/entfernen | `survivalplus.clearitems` |
|
||||||
| `/closedoors <radius>` | Türen schließen | `survivalplus.closedoors` |
|
| `/closedoors <radius>` | Türen in Radius schließen | `survivalplus.closedoors` |
|
||||||
| `/lock` | Container schützen | `survivalplus.lock` |
|
| `/splock lock|unlock|friendadd|friendremove [player]` | Kisten/Türen sperren / freigeben | `survivalplus.lock` |
|
||||||
| `/shop add <item> <preis> <bestand>` | Server-Shop verwalten | `survivalplus.shop` |
|
|
||||||
|
|
||||||
### Statistiken & Reporting
|
### Shop, Loot & Trade
|
||||||
| Befehl | Nutzung | Permission |
|
| Befehl | Usage | Permission |
|
||||||
|--------|---------|------------|
|
|---|---:|:---|
|
||||||
| `/stats` | Spielerstatistiken | `survivalplus.stats` |
|
| `/shop add <item> <basispreis> <lagerbestand>` | Shop verwalten | `survivalplus.shop` |
|
||||||
| `/report <spieler> [grund]` | Spieler melden | `survivalplus.report` |
|
| `/lootchests` | Listet Loot-Kisten auf (Admins teleportieren) | `survivalplus.lootchests` |
|
||||||
| `/showreport <spieler>` | Reports anzeigen | `survivalplus.report.show` |
|
| `/tploot <welt> <x> <y> <z>` | Teleportiere zu Loot-Kiste (Admin) | `survivalplus.lootchests` |
|
||||||
| `/clearreport <spieler>` | Reports löschen | `survivalplus.report.clear` |
|
| `/trade <Spieler>` | Startet Handel | `survivalplus.trade` |
|
||||||
|
| `/tradeaccept <Spieler>` | Akzeptiert Handel | `survivalplus.tradeaccept` |
|
||||||
|
|
||||||
|
### Zeit, Gamemode & Admin
|
||||||
|
| Befehl | Usage | Permission |
|
||||||
|
|---|---:|:---|
|
||||||
|
| `/day` | Setzt Zeit auf Tag | `survivalplus.day` |
|
||||||
|
| `/night` | Setzt Zeit auf Nacht | `survivalplus.night` |
|
||||||
|
| `/gm <modus> [spieler]` (alias `gamemode`) | Spielmodus ändern | `survivalplus.gamemode` |
|
||||||
|
| `/heal [spieler]` | Heilt Spieler (oder andere) | `survivalplus.heal`, `survivalplus.heal.others` |
|
||||||
|
|
||||||
|
### Freundes-, Block- & Report-System
|
||||||
|
| Befehl | Usage | Permission |
|
||||||
|
|---|---:|:---|
|
||||||
|
| `/friend [add/accept/deny/list/del/tp] [Spieler]` | Freundschaften verwalten | `survivalplus.friend` |
|
||||||
|
| `/block <Spieler>` | Spieler blockieren | `survivalplus.block` |
|
||||||
|
| `/unblock <Spieler>` | Unblock | `survivalplus.unblock` |
|
||||||
|
| `/blocklist` | Blockierte Spieler anzeigen | `survivalplus.blocklist` |
|
||||||
|
| `/report <Spieler> [Grund]` | Spieler melden | `survivalplus.report` |
|
||||||
|
| `/showreport <Spieler>` | Reports anzeigen | `survivalplus.report.show` |
|
||||||
|
| `/clearreport <Spieler>` | Reports löschen | `survivalplus.report.clear` |
|
||||||
|
|
||||||
|
### Sonstiges
|
||||||
|
| Befehl | Usage | Permission |
|
||||||
|
|---|---:|:---|
|
||||||
|
| `/stats` | Spielerstatistiken anzeigen | `survivalplus.stats` |
|
||||||
|
| `/kit` | Starterkit erhalten | `survivalplus.kit` |
|
||||||
|
| `/startchallenge <name>` | Fun-Challenge starten | `survivalplus.startchallenge` |
|
||||||
|
| `/lootchests` | Übersicht über Lootkisten | `survivalplus.lootchests` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Permissions (Kurzüberblick)
|
||||||
|
Vollständige Permission-Deklaration findest du in `plugin.yml`.
|
||||||
|
Wichtige Permissions:
|
||||||
|
|
||||||
|
- `survivalplus.*` — Vollzugriff (OP)
|
||||||
|
- `survivalplus.sp` — Zugriff auf `/sp` (Hauptbefehl)
|
||||||
|
- `survivalplus.homes.*` — Homes verwalten
|
||||||
|
- `survivalplus.claim.use` / `survivalplus.claim.trust` — Claim-Management & Trust
|
||||||
|
- `survivalplus.shop` — Shopverwaltung
|
||||||
|
- `survivalplus.lootchests` — Lootchest-Adminrechte
|
||||||
|
- `survivalplus.notify` — Admin-Benachrichtigung bei Besitz von Command/Structure-Blocks
|
||||||
|
- uvm. — siehe `plugin.yml` für die vollständige Liste
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -94,13 +153,17 @@ Es bietet Homes, Teleportation, Inventarverwaltung, Freundeslisten, Shops, Loot-
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Permissions
|
## 🐞 Debug & Fehlerberichte
|
||||||
|
|
||||||
- **Vollzugriff:** `survivalplus.*` (OP)
|
- Aktiviere in `config.yml` `debug-logging: true` wenn du Probleme hast.
|
||||||
- Alle Befehle können über LuckPerms angepasst werden.
|
- `Debug/debug.log` — enthält Plugin-Fehler/Stacktraces (nur wenn aktiviert).
|
||||||
|
- `Debug/console.log` — dupliziert den Konsolenoutput (komplette Ausgabe), damit du diesen als Datei an Entwickler schicken kannst.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Lizenz & Kontakt
|
||||||
|
Dieses Projekt ist frei für den privaten Gebrauch. Für Fragen, Bug-Reports oder Feature-Wünsche: **M_Viper** (Repo-Owner / Gitea).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📜 Lizenz
|
|
||||||
|
|
||||||
Dieses Projekt ist frei für den privaten Gebrauch. Kontakt: **M_Viper**
|
|
||||||
|
@@ -58,13 +58,19 @@ public class TablistManager implements Listener {
|
|||||||
|
|
||||||
// Resource sicherstellen, Config laden
|
// Resource sicherstellen, Config laden
|
||||||
try {
|
try {
|
||||||
plugin.saveResource("tablist.yml", false);
|
File tablistFile = new File(plugin.getDataFolder(), "tablist.yml");
|
||||||
|
if (!tablistFile.exists()) {
|
||||||
|
plugin.saveResource("tablist.yml", false);
|
||||||
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
plugin.reloadTablistConfig();
|
plugin.reloadTablistConfig();
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
FileConfiguration config = plugin.getTablistConfig();
|
FileConfiguration config = plugin.getTablistConfig();
|
||||||
|
|
||||||
|
|
||||||
// Nicknames.yml laden
|
// Nicknames.yml laden
|
||||||
try {
|
try {
|
||||||
File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml");
|
File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml");
|
||||||
|
@@ -66,6 +66,13 @@ import org.bukkit.command.CommandExecutor;
|
|||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import de.viper.survivalplus.util.BannerManager;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class SurvivalPlus extends JavaPlugin {
|
public class SurvivalPlus extends JavaPlugin {
|
||||||
|
|
||||||
@@ -116,6 +123,11 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
private Map<UUID, Integer> playerClaimCounts = new HashMap<>();
|
private Map<UUID, Integer> playerClaimCounts = new HashMap<>();
|
||||||
private Map<UUID, Location> point1Selections = new HashMap<>();
|
private Map<UUID, Location> point1Selections = new HashMap<>();
|
||||||
private Map<UUID, Location> point2Selections = new HashMap<>();
|
private Map<UUID, Location> point2Selections = new HashMap<>();
|
||||||
|
private BannerManager bannerManager;
|
||||||
|
private File debugFile;
|
||||||
|
private PrintWriter debugWriter;
|
||||||
|
private File consoleFile;
|
||||||
|
private PrintWriter consoleWriter;
|
||||||
|
|
||||||
public void reloadTablistConfig() {
|
public void reloadTablistConfig() {
|
||||||
if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml");
|
if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml");
|
||||||
@@ -168,6 +180,41 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
|
||||||
|
// Version aus plugin.yml
|
||||||
|
String pluginVersion = getDescription().getVersion();
|
||||||
|
|
||||||
|
// Sicherstellen, dass 'version:' ganz oben steht
|
||||||
|
ensureConfigVersion(getConfig(), new File(getDataFolder(), "config.yml"), pluginVersion);
|
||||||
|
ensureVersionAtTop(new File(getDataFolder(), "lang.yml"), pluginVersion);
|
||||||
|
ensureVersionAtTop(new File(getDataFolder(), "help.yml"), pluginVersion);
|
||||||
|
ensureVersionAtTop(new File(getDataFolder(), "tablist.yml"), pluginVersion);
|
||||||
|
|
||||||
|
// Debug-Logging initialisieren
|
||||||
|
if (getConfig().getBoolean("debug-logging", false)) {
|
||||||
|
try {
|
||||||
|
// Ordner erstellen
|
||||||
|
File debugFolder = new File(getDataFolder(), "Debug");
|
||||||
|
if (!debugFolder.exists()) {
|
||||||
|
debugFolder.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug.log für Fehler
|
||||||
|
debugFile = new File(debugFolder, "debug.log");
|
||||||
|
if (!debugFile.exists()) debugFile.createNewFile();
|
||||||
|
debugWriter = new PrintWriter(new FileWriter(debugFile, true), true);
|
||||||
|
|
||||||
|
// console.log für komplette Plugin-Infos
|
||||||
|
consoleFile = new File(debugFolder, "console.log");
|
||||||
|
if (!consoleFile.exists()) consoleFile.createNewFile();
|
||||||
|
consoleWriter = new PrintWriter(new FileWriter(consoleFile, true), true);
|
||||||
|
|
||||||
|
logConsole("Debug-Logging ist aktiviert. Alle Plugin-Logs werden in Debug/console.log gespeichert.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warning("Konnte Debug-Dateien nicht erstellen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int pluginId = 26886;
|
int pluginId = 26886;
|
||||||
Metrics metrics = new Metrics(this, pluginId);
|
Metrics metrics = new Metrics(this, pluginId);
|
||||||
metrics.addCustomChart(new SimplePie(
|
metrics.addCustomChart(new SimplePie(
|
||||||
@@ -182,6 +229,10 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
"plugin_language",
|
"plugin_language",
|
||||||
() -> getConfig().getString("language", "default")
|
() -> getConfig().getString("language", "default")
|
||||||
));
|
));
|
||||||
|
|
||||||
|
bannerManager = new BannerManager(this);
|
||||||
|
bannerManager.displayConsoleBanner(); // Banner beim Serverstart
|
||||||
|
|
||||||
updateConfigFiles();
|
updateConfigFiles();
|
||||||
createHomesFile();
|
createHomesFile();
|
||||||
createGravesFile();
|
createGravesFile();
|
||||||
@@ -383,6 +434,77 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
getLogger().info("Block-Regeln angewendet: CommandBlocks erlaubt=" + cmdAllowed + ", StructureBlocks erlaubt=" + structAllowed);
|
getLogger().info("Block-Regeln angewendet: CommandBlocks erlaubt=" + cmdAllowed + ", StructureBlocks erlaubt=" + structAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureVersionAtTop(File file, String version) {
|
||||||
|
try {
|
||||||
|
if (!file.exists()) return; // Datei existiert nicht
|
||||||
|
List<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if (!lines.isEmpty() && lines.get(0).startsWith("version:")) {
|
||||||
|
// Alte Versionsnummer ersetzen
|
||||||
|
lines.set(0, "version: " + version);
|
||||||
|
} else {
|
||||||
|
// Version an den Anfang setzen
|
||||||
|
lines.add(0, "version: " + version);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.write(file.toPath(), lines, StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warning("Konnte Version in " + file.getName() + " nicht setzen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speziell für config.yml:
|
||||||
|
* - Setzt die Version in der geladenen FileConfiguration (getConfig()) und speichert sie,
|
||||||
|
* - und sorgt anschließend dafür, dass die 'version:'-Zeile ganz oben in der Datei steht.
|
||||||
|
*/
|
||||||
|
private void ensureConfigVersion(FileConfiguration config, File file, String version) {
|
||||||
|
try {
|
||||||
|
if (!file.exists()) return; // nichts zu tun, Datei existiert nicht
|
||||||
|
|
||||||
|
// 1) Version in der geladenen Config setzen und speichern (Bukkit nutzt diese Config im Speicher)
|
||||||
|
String current = config.getString("version", "");
|
||||||
|
if (!version.equals(current)) {
|
||||||
|
config.set("version", version);
|
||||||
|
try {
|
||||||
|
config.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warning("Konnte config.yml nicht speichern: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Text-Nachbearbeitung: sicherstellen, dass 'version:' als erste Zeile steht
|
||||||
|
List<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// Entferne alle vorhandenen version:-Zeilen (falls irgendwo)
|
||||||
|
int foundIndex = -1;
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
if (lines.get(i).trim().startsWith("version:")) {
|
||||||
|
foundIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundIndex == 0) {
|
||||||
|
// bereits oben -> ersetzen
|
||||||
|
lines.set(0, "version: " + version);
|
||||||
|
} else {
|
||||||
|
// entferne alte Stelle (wenn vorhanden)
|
||||||
|
if (foundIndex > 0) {
|
||||||
|
lines.remove(foundIndex);
|
||||||
|
}
|
||||||
|
// füge neue version ganz oben ein
|
||||||
|
lines.add(0, "version: " + version);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.write(file.toPath(), lines, StandardCharsets.UTF_8);
|
||||||
|
getLogger().info("config.yml: Version oben gesetzt auf " + version);
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warning("Konnte Version in config.yml nicht setzen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeForbiddenBlocksFromInventories(boolean cmdAllowed, boolean structAllowed) {
|
private void removeForbiddenBlocksFromInventories(boolean cmdAllowed, boolean structAllowed) {
|
||||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||||
if (p.hasPermission("survivalplus.notify")) {
|
if (p.hasPermission("survivalplus.notify")) {
|
||||||
@@ -558,19 +680,6 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Methoden für nicknames.yml
|
// Methoden für nicknames.yml
|
||||||
public void createNicknamesFile() {
|
public void createNicknamesFile() {
|
||||||
nicknamesFile = new File(getDataFolder(), "nicknames.yml");
|
nicknamesFile = new File(getDataFolder(), "nicknames.yml");
|
||||||
@@ -618,6 +727,9 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
if (debugWriter != null) {
|
||||||
|
debugWriter.close();
|
||||||
|
}
|
||||||
if (autoClearTaskId != -1) {
|
if (autoClearTaskId != -1) {
|
||||||
Bukkit.getScheduler().cancelTask(autoClearTaskId);
|
Bukkit.getScheduler().cancelTask(autoClearTaskId);
|
||||||
}
|
}
|
||||||
@@ -1066,4 +1178,34 @@ public class SurvivalPlus extends JavaPlugin {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public BannerManager getBannerManager() {
|
||||||
|
return bannerManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur Fehler/Exceptions
|
||||||
|
public void log(String msg) {
|
||||||
|
if (getConfig().getBoolean("debug-logging", false) && debugWriter != null) {
|
||||||
|
debugWriter.println("[" + new java.util.Date() + "] " + msg);
|
||||||
|
} else {
|
||||||
|
getLogger().info(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logError(String msg, Throwable t) {
|
||||||
|
if (getConfig().getBoolean("debug-logging", false) && debugWriter != null) {
|
||||||
|
debugWriter.println("[" + new java.util.Date() + "] ERROR: " + msg);
|
||||||
|
t.printStackTrace(debugWriter);
|
||||||
|
} else {
|
||||||
|
getLogger().log(Level.SEVERE, msg, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Komplette Plugin-Logs in console.log
|
||||||
|
public void logConsole(String msg) {
|
||||||
|
if (getConfig().getBoolean("debug-logging", false) && consoleWriter != null) {
|
||||||
|
consoleWriter.println("[" + new java.util.Date() + "] " + msg);
|
||||||
|
}
|
||||||
|
getLogger().info(msg); // Optional: zusätzlich Konsole ausgeben
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -2,13 +2,20 @@ package de.viper.survivalplus.listeners;
|
|||||||
|
|
||||||
import de.viper.survivalplus.SurvivalPlus;
|
import de.viper.survivalplus.SurvivalPlus;
|
||||||
import de.viper.survivalplus.util.Claim;
|
import de.viper.survivalplus.util.Claim;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.block.BlockBreakEvent;
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ClaimListener implements Listener {
|
public class ClaimListener implements Listener {
|
||||||
private SurvivalPlus plugin;
|
private final SurvivalPlus plugin;
|
||||||
|
private final Map<UUID, Claim> lastClaim = new HashMap<>();
|
||||||
|
|
||||||
public ClaimListener(SurvivalPlus plugin) {
|
public ClaimListener(SurvivalPlus plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@@ -16,19 +23,51 @@ public class ClaimListener implements Listener {
|
|||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onBlockBreak(BlockBreakEvent event) {
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.isOp()) return;
|
||||||
Claim claim = plugin.getClaim(event.getBlock().getLocation());
|
Claim claim = plugin.getClaim(event.getBlock().getLocation());
|
||||||
if (claim != null && !claim.canInteract(event.getPlayer().getUniqueId()) && !event.getPlayer().isOp()) {
|
if (claim != null && !claim.getOwner().equals(player.getUniqueId()) && !claim.getTrusted().contains(player.getUniqueId())) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
event.getPlayer().sendMessage(plugin.getMessage("claim.no-break"));
|
player.sendMessage(plugin.getMessage("claim.no-break"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onBlockPlace(BlockPlaceEvent event) {
|
public void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.isOp()) return;
|
||||||
Claim claim = plugin.getClaim(event.getBlock().getLocation());
|
Claim claim = plugin.getClaim(event.getBlock().getLocation());
|
||||||
if (claim != null && !claim.canInteract(event.getPlayer().getUniqueId()) && !event.getPlayer().isOp()) {
|
if (claim != null && !claim.getOwner().equals(player.getUniqueId()) && !claim.getTrusted().contains(player.getUniqueId())) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
event.getPlayer().sendMessage(plugin.getMessage("claim.no-place"));
|
player.sendMessage(plugin.getMessage("claim.no-place"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
UUID playerId = player.getUniqueId();
|
||||||
|
Claim currentClaim = plugin.getClaim(event.getTo());
|
||||||
|
|
||||||
|
// Prüfe, ob sich der Claim-Status geändert hat
|
||||||
|
Claim previousClaim = lastClaim.getOrDefault(playerId, null);
|
||||||
|
if (currentClaim != previousClaim) {
|
||||||
|
if (currentClaim != null) {
|
||||||
|
// Spieler betritt einen Claim
|
||||||
|
String ownerName = plugin.getServer().getOfflinePlayer(currentClaim.getOwner()).getName();
|
||||||
|
if (ownerName == null) {
|
||||||
|
ownerName = currentClaim.getOwner().toString(); // Fallback auf UUID
|
||||||
|
}
|
||||||
|
player.sendMessage(plugin.getMessage("claim.enter").replace("%owner%", ownerName));
|
||||||
|
} else if (previousClaim != null) {
|
||||||
|
// Spieler verlässt einen Claim
|
||||||
|
String ownerName = plugin.getServer().getOfflinePlayer(previousClaim.getOwner()).getName();
|
||||||
|
if (ownerName == null) {
|
||||||
|
ownerName = previousClaim.getOwner().toString(); // Fallback auf UUID
|
||||||
|
}
|
||||||
|
player.sendMessage(plugin.getMessage("claim.leave").replace("%owner%", ownerName));
|
||||||
|
}
|
||||||
|
lastClaim.put(playerId, currentClaim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -26,10 +26,12 @@ public class NewbieProtectionListener implements Listener {
|
|||||||
private final SurvivalPlus plugin;
|
private final SurvivalPlus plugin;
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
private final int durationMinutes;
|
private final int durationMinutes;
|
||||||
|
private final int COOLDOWN_SECONDS = 60; // Abklingzeit für Nachrichten in Sekunden (1 Minute)
|
||||||
|
|
||||||
// Maps für Zeiten und BossBars
|
// Maps für Zeiten, BossBars und Nachrichten-Abklingzeit
|
||||||
private final Map<UUID, Integer> remainingSeconds = new HashMap<>();
|
private final Map<UUID, Integer> remainingSeconds = new HashMap<>();
|
||||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||||
|
private final Map<UUID, Long> messageCooldowns = new HashMap<>();
|
||||||
|
|
||||||
// YAML Datei
|
// YAML Datei
|
||||||
private File dataFile;
|
private File dataFile;
|
||||||
@@ -89,14 +91,29 @@ public class NewbieProtectionListener implements Listener {
|
|||||||
if (!(event.getEntity() instanceof Player)) return;
|
if (!(event.getEntity() instanceof Player)) return;
|
||||||
|
|
||||||
Player victim = (Player) event.getEntity();
|
Player victim = (Player) event.getEntity();
|
||||||
UUID vid = victim.getUniqueId();
|
UUID victimId = victim.getUniqueId();
|
||||||
Integer timeLeft = remainingSeconds.get(vid);
|
Integer timeLeft = remainingSeconds.get(victimId);
|
||||||
|
|
||||||
if (timeLeft != null && timeLeft > 0) {
|
if (timeLeft != null && timeLeft > 0) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
victim.sendMessage(ChatColor.GREEN + "Du bist noch im Neulingsschutz!");
|
|
||||||
|
// Prüfe Abklingzeit für Opfer
|
||||||
|
long currentTime = System.currentTimeMillis() / 1000;
|
||||||
|
long lastMessageTimeVictim = messageCooldowns.getOrDefault(victimId, 0L);
|
||||||
|
if (currentTime >= lastMessageTimeVictim + COOLDOWN_SECONDS) {
|
||||||
|
victim.sendMessage(ChatColor.GREEN + "Du bist noch im Neulingsschutz!");
|
||||||
|
messageCooldowns.put(victimId, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe Abklingzeit für Angreifer, falls es ein Spieler ist
|
||||||
if (event.getDamager() instanceof Player) {
|
if (event.getDamager() instanceof Player) {
|
||||||
((Player) event.getDamager()).sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!");
|
Player damager = (Player) event.getDamager();
|
||||||
|
UUID damagerId = damager.getUniqueId();
|
||||||
|
long lastMessageTimeDamager = messageCooldowns.getOrDefault(damagerId, 0L);
|
||||||
|
if (currentTime >= lastMessageTimeDamager + COOLDOWN_SECONDS) {
|
||||||
|
damager.sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!");
|
||||||
|
messageCooldowns.put(damagerId, currentTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +175,6 @@ public class NewbieProtectionListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WICHTIG: Public gemacht, damit von SurvivalPlus.java aufrufbar
|
|
||||||
public void saveData() {
|
public void saveData() {
|
||||||
if (dataConfig == null) return;
|
if (dataConfig == null) return;
|
||||||
for (Map.Entry<UUID, Integer> entry : remainingSeconds.entrySet()) {
|
for (Map.Entry<UUID, Integer> entry : remainingSeconds.entrySet()) {
|
||||||
@@ -176,4 +192,4 @@ public class NewbieProtectionListener implements Listener {
|
|||||||
int sec = totalSec % 60;
|
int sec = totalSec % 60;
|
||||||
return String.format("%02d:%02d", min, sec);
|
return String.format("%02d:%02d", min, sec);
|
||||||
}
|
}
|
||||||
}
|
}
|
28
src/main/java/de/viper/survivalplus/util/BannerManager.java
Normal file
28
src/main/java/de/viper/survivalplus/util/BannerManager.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package de.viper.survivalplus.util;
|
||||||
|
|
||||||
|
import de.viper.survivalplus.SurvivalPlus;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BannerManager {
|
||||||
|
private final SurvivalPlus plugin;
|
||||||
|
|
||||||
|
public BannerManager(SurvivalPlus plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayConsoleBanner() {
|
||||||
|
String version = plugin.getDescription().getVersion();
|
||||||
|
List<String> banner = Arrays.asList(
|
||||||
|
"******************************",
|
||||||
|
"* SurvivalPlus " + version + " *",
|
||||||
|
"* *",
|
||||||
|
"* M_Viper *",
|
||||||
|
"******************************"
|
||||||
|
);
|
||||||
|
for (String line : banner) {
|
||||||
|
plugin.getLogger().info(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,9 @@
|
|||||||
|
# Version (Nicht Ändern!)
|
||||||
|
version: 1.0.9
|
||||||
|
|
||||||
|
# Debug-Option
|
||||||
|
debug-logging: false
|
||||||
|
|
||||||
# Neulings Schutz
|
# Neulings Schutz
|
||||||
newbie-protection:
|
newbie-protection:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@@ -403,24 +403,30 @@ inventory:
|
|||||||
player-only: "§cDieser Befehl ist nur für Spieler!"
|
player-only: "§cDieser Befehl ist nur für Spieler!"
|
||||||
|
|
||||||
claim:
|
claim:
|
||||||
points-not-set: "&cDu musst zuerst zwei Punkte markieren! Verwende /claim mark <1|2>."
|
points-not-set: "&cDu musst zuerst zwei Punkte markieren! Verwende /claim mark <1|2>."
|
||||||
different-worlds: "&cDie markierten Punkte müssen in derselben Welt liegen!"
|
different-worlds: "&cDie markierten Punkte müssen in derselben Welt liegen!"
|
||||||
too-large: "&cDer Bereich ist zu groß! Maximal erlaubt: %max% Blöcke²."
|
too-large: "&cDer Bereich ist zu groß! Maximal erlaubt: %max% Blöcke²."
|
||||||
overlap: "&cDieser Bereich überschneidet sich mit einem bestehenden Claim!"
|
overlap: "&cDieser Bereich überschneidet sich mit einem bestehenden Claim!"
|
||||||
max-reached: "&cDu hast das Maximum von %max% Claims erreicht!"
|
max-reached: "&cDu hast das Maximum von %max% Claims erreicht!"
|
||||||
success: "&aBereich beansprucht! (%count%/%max%)"
|
success: "&aBereich beansprucht! (%count%/%max%)"
|
||||||
unclaimed: "&aBereich freigegeben!"
|
unclaimed: "&aBereich freigegeben!"
|
||||||
not-owner: "&cDies ist nicht dein Claim!"
|
not-owner: "&cDies ist nicht dein Claim!"
|
||||||
point1-set: "&aPunkt 1 gesetzt bei x=%x%, z=%z%."
|
point1-set: "&aPunkt 1 gesetzt bei x=%x%, z=%z%."
|
||||||
point2-set: "&aPunkt 2 gesetzt bei x=%x%, z=%z%."
|
point2-set: "&aPunkt 2 gesetzt bei x=%x%, z=%z%."
|
||||||
trusted: "&a%player% ist jetzt in diesem Claim vertraut!"
|
trusted: "&a%player% ist jetzt in diesem Claim vertraut!"
|
||||||
untrusted: "&a%player% ist in diesem Claim nicht mehr vertraut!"
|
untrusted: "&a%player% ist in diesem Claim nicht mehr vertraut!"
|
||||||
no-break: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke abbauen!"
|
no-break: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke abbauen!"
|
||||||
no-place: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke platzieren!"
|
no-place: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke platzieren!"
|
||||||
op-unclaimed: "&a%count% Claims von %player% wurden entfernt!"
|
op-unclaimed: "&a%count% Claims von %player% wurden entfernt!"
|
||||||
no-claims-found: "&cKeine Claims für %player% gefunden!"
|
no-claims-found: "&cKeine Claims für %player% gefunden!"
|
||||||
no-claim-at-location: "&cKein Claim an dieser Position!"
|
no-claim-at-location: "&cKein Claim an dieser Position!"
|
||||||
info: "&eClaim-Info:\n&7Besitzer: &e%owner%\n&7Welt: &e%world%\n&7Koordinaten: &eX1: %x1%, Z1: %z1% bis X2: %x2%, Z2: %z2%"
|
info:
|
||||||
|
- "&eClaim-Info:"
|
||||||
|
- "&7Besitzer: &e%owner%"
|
||||||
|
- "&7Welt: &e%world%"
|
||||||
|
- "&7Koordinaten: &eX1: %x1%, Z1: %z1% bis X2: %x2%, Z2: %z2%"
|
||||||
|
enter: "&aDu hast das Gebiet von %owner% betreten."
|
||||||
|
leave: "&eDu hast das Gebiet von %owner% verlassen."
|
||||||
|
|
||||||
force-survival:
|
force-survival:
|
||||||
join-message: "§aDu wurdest in den Survivalmodus gesetzt!"
|
join-message: "§aDu wurdest in den Survivalmodus gesetzt!"
|
@@ -1,5 +1,6 @@
|
|||||||
name: SurvivalPlus
|
name: SurvivalPlus
|
||||||
version: 1.0.8
|
version: 1.0.9
|
||||||
|
|
||||||
|
|
||||||
main: de.viper.survivalplus.SurvivalPlus
|
main: de.viper.survivalplus.SurvivalPlus
|
||||||
api-version: 1.21
|
api-version: 1.21
|
||||||
|
Reference in New Issue
Block a user