1 Commits
1.1.1 ... 1.0.8

Author SHA1 Message Date
77ae1d6ed9 Update: exclude target/ and *.zip 2025-08-26 08:09:02 +02:00
35 changed files with 769 additions and 2319 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignoriere Build-Ordner
target/
# Ignoriere ZIP-Dateien
*.zip

Binary file not shown.

29
pom.xml
View File

@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.viper</groupId>
<artifactId>SurvivalPlus</artifactId>
<version>1.1.1-Beta</version>
<version>1.0.8-Beta</version>
<packaging>jar</packaging>
<name>SurvivalPlus</name>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -22,6 +22,7 @@
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- Repository für PlaceholderAPI -->
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
@@ -29,14 +30,12 @@
</repositories>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- bStats Bukkit Dependency -->
<dependency>
<groupId>org.bstats</groupId>
@@ -44,7 +43,6 @@
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<!-- PlaceholderAPI Dependency -->
<dependency>
<groupId>me.clip</groupId>
@@ -52,7 +50,6 @@
<version>2.11.6</version>
<scope>provided</scope>
</dependency>
<!-- LuckPerms API Dependency -->
<dependency>
<groupId>net.luckperms</groupId>
@@ -60,19 +57,10 @@
<version>5.5.10</version>
<scope>provided</scope>
</dependency>
<!-- ProtocolLib 4.8.0 (für WrapperPlayServerScoreboardTeam) -->
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.8.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -82,8 +70,6 @@
<target>17</target>
</configuration>
</plugin>
<!-- Shade Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
@@ -127,9 +113,6 @@
<include>tablist.yml</include>
<include>blockedcommands.yml</include>
<include>claims.yml</include>
<include>lootchests.yml</include>
<include>blocks.yml</include>
<include>nicknames.yml</include>
</includes>
</resource>
</resources>

192
readme.md
View File

@@ -1,151 +1,87 @@
# SurvivalPlus
![Minecraft Plugin](https://img.shields.io/badge/Minecraft-Plugin-green)
![Version](https://img.shields.io/badge/version-1.1.1-blue)
![Version](https://img.shields.io/badge/version-1.0.5-blue)
![Author](https://img.shields.io/badge/author-M_Viper-yellow)
**SurvivalPlus** ist ein Minecraft-Plugin zur Verbesserung des Survival-Erlebnisses.
Es bietet Homes, Teleports, Inventar-/Enderchest-Verwaltung, Claims (Anti-Grief), Freundeslisten, Shop-System, Loot-Kisten, Tablist-Anpassungen und viele Komfort-Utilities.
Es bietet Homes, Teleportation, Inventarverwaltung, Freundeslisten, Shops, Loot-Kisten und weitere Komfortbefehle.
---
## ⚡ Highlights / Features
## ⚡ Features
- Homes & Warps (persönliche Warps, Home-Management)
- Teleportation & Anfrage-System (`/tpa`, `/tpaccept`, `/tpdeny`)
- Claim-System zur Land-Sicherung (trust/untrust, create/delete, info, list)
- Inventar- & Enderchest-Verwaltung (öffnen von Fremd-Inventaren, Admin-Tools)
- Shop-System & Lootchests (Verwaltung, Teleport zu Lootchests)
- CommandBlocker & Server-Utilities (clearchat, clearitems, closedoors, lock)
- Debug-Logging (optionale `debug-logging` in config, Debug/debug.log & Debug/console.log)
- Tablist (animiert / konfigurierbar über `tablist.yml`)
- bStats Unterstützung (Statistiken)
- **Homes & Warps** Homepunkte setzen, löschen, Teleportation, persönliche Warps
- **Teleportation & Spielerinteraktion** `/tp`, `/tphere`, `/tpa`, `/back`, `/spawn`
- **Inventar & Endertruhe** Öffnen eigener und fremder Inventare/Endertruhen
- **Spielmodus & Zeit** `/gm`, `/day`, `/night`
- **Freundes- und Kommunikationssystem** `/friend`, `/block`, `/blocklist`
- **Koordinaten teilen** `/sp share`, `/sp shareconfirm`, `/sp sharecancel`
- **Items & Werkzeuge** `/ir`, `/workbench`, `/anvil`, `/kit`
- **Server-Management** `/clearchat`, `/clearitems`, `/closedoors`, `/lock`, `/shop`
- **Statistiken & Reporting** `/stats`, `/report`, `/showreport`, `/clearreport`
- **Fun & Challenges** `/sit`, `/startchallenge`, `/trade`, `/lootchests`
---
## 🛠 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` |
| `/nick off` | Entfernt den Nickname wieder (Zurücksetzen zum echten Namen). | `survivalplus.nick` |
| `/nick reset` | Alias für /nick off | `survivalplus.nick` |
| `/nick remove` | Alias für /nick off | `survivalplus.nick` |
## 🛠 Commands Übersicht
### Teleportation
| Befehl | Usage | Permission |
|---|---:|:---|
| `/tp <Spieler>` | Teleport zu Spieler | `survivalplus.tp` |
| `/tphere <Spieler>` | Teleportiert Spieler zu dir | `survivalplus.tphere` |
| `/tpa <Spieler>` | Teleportanfrage senden | `survivalplus.tpa` |
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/tp` | Teleport zu Spieler | `survivalplus.tp` |
| `/tphere` | Spieler zu dir teleportieren | `survivalplus.tphere` |
| `/tpa` | Teleportanfrage senden | `survivalplus.tpa` |
| `/tpaccept` | Teleportanfrage annehmen | `survivalplus.tpaccept` |
| `/tpdeny` | Teleportanfrage ablehnen | `survivalplus.tpdeny` |
| `/back` | Zum letzten Todespunkt teleportieren | `survivalplus.back` |
| `/spawn` | Teleport zum Weltspawn | `survivalplus.spawn` |
| `/setspawn` | Setzt Server-Spawn | `survivalplus.setspawn` |
| `/setworldspawn` | Setzt Weltspawn (aktuelle Welt) | `survivalplus.setworldspawn` |
| `/back` | Zum letzten Todespunkt | `survivalplus.back` |
| `/spawn` | Weltspawn teleport | `survivalplus.spawn` |
### Homes & Warps
| Befehl | Usage | Permission |
|---|---:|:---|
| `/sethome <name>` | Setzt Home | `survivalplus.homes.set` |
| `/delhome <name>` | Löscht Home | `survivalplus.homes.delete` |
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/sethome <name>` | Home setzen | `survivalplus.homes.set` |
| `/delhome <name>` | Home löschen | `survivalplus.homes.delete` |
| `/home <name>` | Teleport zu Home | `survivalplus.homes` |
| `/homelist` | GUI mit Homes öffnen | `survivalplus.homes.list` |
| `/setwarp <name>` | Persönlichen Warp setzen (Item in Hand) | `survivalplus.setwarp` |
| `/homelist` | Liste aller Homes | `survivalplus.homes.list` |
| `/setwarp <name>` | Persönlichen Warp setzen | `survivalplus.setwarp` |
| `/delwarp <name>` | Persönlichen Warp löschen | `survivalplus.delwarp` |
| `/warps` | GUI aller Warps öffnen | `survivalplus.warps` |
### Claim (Anti-Grief)
| Befehl | Usage | Permission |
|---|---:|:---|
| `/claim [unclaim / trust <player> / untrust <player>]` | Claim-Management (Trust/Untrust/Unclaim) | `survivalplus.claim.use` |
| `/claim create <name>` (falls implementiert) | Neues Claim anlegen | `survivalplus.claim.use` |
| `/claim delete <name>` (falls implementiert) | Claim löschen | `survivalplus.claim.use` |
| `/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` |
### Spieler & Kommunikation
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/friend` | Freundesliste verwalten | `survivalplus.friend` |
| `/block` | Spieler blockieren | `survivalplus.block` |
| `/unblock` | Blockierung aufheben | `survivalplus.unlock` |
| `/blocklist` | Liste blockierter Spieler | `survivalplus.blocklist` |
### CommandBlocker / Server-Management
| Befehl | Usage | Permission |
|---|---:|:---|
| `/sp cb add <cmd>` | Befehl zur Blockliste hinzufügen | `survivalplus.commandblocker.add` |
| `/sp cb remove <cmd>` | Befehl entfernen | `survivalplus.commandblocker.remove` |
| `/sp cb list` | Blockierte Befehle anzeigen | `survivalplus.commandblocker.list` |
| `/clearchat` | Chat löschen | `survivalplus.clearchat` |
| `/clearitems` | Items aufsammeln/entfernen | `survivalplus.clearitems` |
| `/closedoors <radius>` | Türen in Radius schließen | `survivalplus.closedoors` |
| `/sp lock /unlock/friendadd/friendremove [player]` | Kisten/Türen sperren / freigeben | `survivalplus.lock` |
### Shop, Loot & Trade
| Befehl | Usage | Permission |
|---|---:|:---|
| `/shop add <item> <basispreis> <lagerbestand>` | Shop verwalten | `survivalplus.shop` |
| `/lootchests` | Listet Loot-Kisten auf (Admins teleportieren) | `survivalplus.lootchests` |
| `/tploot <welt> <x> <y> <z>` | Teleportiere zu Loot-Kiste (Admin) | `survivalplus.lootchests` |
| `/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` |
### Items & Werkzeuge
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/ir <neuer_name>` | Item umbenennen | `survivalplus.itemrename` |
| `/workbench` | Werkbank GUI öffnen | `survivalplus.workbench` |
| `/anvil` | Amboss GUI öffnen | `survivalplus.anvil` |
| `/kit` | Starterkit erhalten | `survivalplus.kit` |
| `/startchallenge <name>` | Fun-Challenge starten | `survivalplus.startchallenge` |
| `/lootchests` | Übersicht über Lootkisten | `survivalplus.lootchests` |
| `/sit` | Hinsetzen | `survivalplus.sit` |
| `/ride` | Auf spieler Reiten | `survivalplus.ride` |
| `/leashcount` | Anzahl geleinter Tiere | `survivalplus.leashcount` |
| `/nick <Name>` | Nickname ändern | `survivalplus.nick` |
---
### Server-Management
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/clearchat` | Chat löschen | `survivalplus.clearchat` |
| `/clearitems` | Items entfernen | `survivalplus.clearitems` |
| `/closedoors <radius>` | Türen schließen | `survivalplus.closedoors` |
| `/lock` | Container schützen | `survivalplus.lock` |
| `/shop add <item> <preis> <bestand>` | Server-Shop verwalten | `survivalplus.shop` |
## 🔐 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
### Statistiken & Reporting
| Befehl | Nutzung | Permission |
|--------|---------|------------|
| `/stats` | Spielerstatistiken | `survivalplus.stats` |
| `/report <spieler> [grund]` | Spieler melden | `survivalplus.report` |
| `/showreport <spieler>` | Reports anzeigen | `survivalplus.report.show` |
| `/clearreport <spieler>` | Reports löschen | `survivalplus.report.clear` |
---
@@ -158,17 +94,13 @@ Wichtige Permissions:
---
## 🐞 Debug & Fehlerberichte
## 🔐 Permissions
- Aktiviere in `config.yml` `debug-logging: true` wenn du Probleme hast.
- `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).
- **Vollzugriff:** `survivalplus.*` (OP)
- Alle Befehle können über LuckPerms angepasst werden.
---
## 📜 Lizenz
Dieses Projekt ist frei für den privaten Gebrauch. Kontakt: **M_Viper**

View File

@@ -1,83 +1,30 @@
package de.viper.survivalplus.Manager;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.util.*;
import de.viper.survivalplus.SurvivalPlus;
import org.bukkit.entity.Player;
public class BlockManager {
private final SurvivalPlus plugin;
private final Map<UUID, Set<UUID>> blockedPlayers = new HashMap<>();
private File blocksFile;
private FileConfiguration blocksConfig;
public BlockManager(SurvivalPlus plugin) {
this.plugin = plugin;
this.blocksFile = new File(plugin.getDataFolder(), "blocks.yml");
if (!blocksFile.exists()) {
try {
blocksFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
this.blocksConfig = YamlConfiguration.loadConfiguration(blocksFile);
loadBlocks();
}
private void saveBlocks() {
for (UUID key : blockedPlayers.keySet()) {
List<String> uuids = new ArrayList<>();
for (UUID uuid : blockedPlayers.get(key)) {
uuids.add(uuid.toString());
}
blocksConfig.set(key.toString(), uuids);
}
try {
blocksConfig.save(blocksFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private void loadBlocks() {
blockedPlayers.clear();
for (String key : blocksConfig.getKeys(false)) {
UUID blockerUUID = UUID.fromString(key);
List<String> blockedUUIDs = blocksConfig.getStringList(key);
Set<UUID> blockedSet = new HashSet<>();
for (String uuidStr : blockedUUIDs) {
blockedSet.add(UUID.fromString(uuidStr));
}
blockedPlayers.put(blockerUUID, blockedSet);
}
}
public void blockPlayer(Player blocker, Player toBlock) {
blockedPlayers.computeIfAbsent(blocker.getUniqueId(), k -> new HashSet<>()).add(toBlock.getUniqueId());
saveBlocks(); // Sofort speichern
}
public void unblockPlayer(Player blocker, Player toUnblock) {
Set<UUID> blocked = blockedPlayers.get(blocker.getUniqueId());
if (blocked != null) {
blocked.remove(toUnblock.getUniqueId());
if (blocked.isEmpty()) {
blockedPlayers.remove(blocker.getUniqueId());
}
saveBlocks(); // Sofort speichern
Set<UUID> blocked = blockedPlayers.get(blocker.getUniqueId());
if (blocked != null) {
blocked.remove(toUnblock.getUniqueId());
if (blocked.isEmpty()) {
blockedPlayers.remove(blocker.getUniqueId());
}
}
}
public boolean hasBlocked(Player blocker, Player potentialBlocked) {
return blockedPlayers.getOrDefault(blocker.getUniqueId(), Collections.emptySet())
.contains(potentialBlocked.getUniqueId());
.contains(potentialBlocked.getUniqueId());
}
public Set<UUID> getBlockedPlayers(Player player) {
@@ -86,6 +33,5 @@ public class BlockManager {
public void clear(Player player) {
blockedPlayers.remove(player.getUniqueId());
saveBlocks();
}
}

View File

@@ -6,21 +6,18 @@ import org.bukkit.*;
import org.bukkit.block.Chest;
import org.bukkit.command.*;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@@ -37,17 +34,9 @@ public class LootChestManager implements Listener, CommandExecutor {
private final int maxLootPerPlayer;
private final long lootLimitResetMillis;
// Persistence
private final File lootChestFile;
private FileConfiguration lootChestConfig;
public LootChestManager(SurvivalPlus plugin) {
this.plugin = plugin;
this.lootChestFile = new File(plugin.getDataFolder(), "lootchests.yml");
this.lootChestConfig = YamlConfiguration.loadConfiguration(lootChestFile);
loadLootFromConfig();
loadActiveChests(); // Kisten beim Start laden
FileConfiguration cfg = plugin.getConfig();
this.despawnMillis = cfg.getLong("lootchest.despawn-minutes", 10) * 60 * 1000;
@@ -60,102 +49,6 @@ public class LootChestManager implements Listener, CommandExecutor {
startLootLimitResetTask();
}
/**
* Lädt gespeicherte Kisten aus der Datei und prüft, ob sie abgelaufen sind
*/
private void loadActiveChests() {
if (!lootChestFile.exists()) return;
long now = System.currentTimeMillis();
List<Location> toRemoveFile = new ArrayList<>();
for (String key : lootChestConfig.getKeys(false)) {
try {
long spawnTime = lootChestConfig.getLong(key);
// Prüfen ob die Zeit abgelaufen ist (inklusive Offline-Zeit)
if (now - spawnTime >= despawnMillis) {
String[] parts = key.split("_");
if (parts.length == 4) {
World w = Bukkit.getWorld(parts[0]);
if (w != null) {
int x = Integer.parseInt(parts[1]);
int y = Integer.parseInt(parts[2]);
int z = Integer.parseInt(parts[3]);
Location loc = new Location(w, x, y, z);
// Kiste physikalisch löschen falls noch vorhanden
if (loc.getBlock().getType() == Material.CHEST) {
loc.getBlock().setType(Material.AIR);
}
toRemoveFile.add(loc);
}
}
} else {
// Kiste ist noch gültig -> in den Speicher laden
String[] parts = key.split("_");
if (parts.length == 4) {
World w = Bukkit.getWorld(parts[0]);
if (w != null) {
int x = Integer.parseInt(parts[1]);
int y = Integer.parseInt(parts[2]);
int z = Integer.parseInt(parts[3]);
Location loc = new Location(w, x, y, z);
// Integritätsprüfung: Ist da auch wirklich eine Kiste?
if (loc.getBlock().getType() == Material.CHEST) {
activeChests.add(loc);
chestSpawnTimes.put(loc, spawnTime);
} else {
// Datei sagt ja, Welt sagt nein -> Cleanup in Datei
toRemoveFile.add(loc);
}
}
}
}
} catch (Exception e) {
plugin.getLogger().warning("Fehler beim Laden einer Lootkiste: " + key);
}
}
// Alte Einträge aus der Datei entfernen
for (Location loc : toRemoveFile) {
String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ();
lootChestConfig.set(key, null);
}
saveActiveChests();
}
/**
* Speichert die aktuellen aktiven Kisten in die Datei
*/
private void saveActiveChests() {
lootChestConfig = new YamlConfiguration();
for (Map.Entry<Location, Long> entry : chestSpawnTimes.entrySet()) {
Location loc = entry.getKey();
String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ();
lootChestConfig.set(key, entry.getValue());
}
try {
lootChestConfig.save(lootChestFile);
} catch (IOException e) {
plugin.getLogger().severe("Konnte lootchests.yml nicht speichern!");
e.printStackTrace();
}
}
/**
* Entfernt eine Kiste komplett aus System und Datei (z.B. beim Looten)
*/
private void removeChestData(Location loc) {
activeChests.remove(loc);
chestSpawnTimes.remove(loc);
String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ();
lootChestConfig.set(key, null);
saveActiveChests();
}
private void loadLootFromConfig() {
FileConfiguration cfg = plugin.getConfig();
lootItems.clear();
@@ -182,11 +75,7 @@ public class LootChestManager implements Listener, CommandExecutor {
new BukkitRunnable() {
@Override
public void run() {
// Falls deaktiviert, nichts tun
// Hinweis: Die Logik hier war "!enabled ... return; clearAll".
// Das bedeutet: Wenn enabled=true -> !enabled=false -> kein return -> clearAll.
if (!plugin.getConfig().getBoolean("lootchest.enabled", true)) return;
clearAllActiveChests();
for (int i = 0; i < spawnCount; i++) {
spawnRandomLootChest();
@@ -199,36 +88,26 @@ public class LootChestManager implements Listener, CommandExecutor {
}
private void startChestDespawnTask() {
// Prüft jede Minute (20 Ticks * 60)
new BukkitRunnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
// Sicheres Iterieren um ConcurrentModificationException zu vermeiden
Iterator<Location> it = activeChests.iterator();
while (it.hasNext()) {
Location loc = it.next();
Long spawnTime = chestSpawnTimes.get(loc);
if (spawnTime != null && now - spawnTime >= despawnMillis) {
if (loc.getBlock().getType() == Material.CHEST) {
loc.getBlock().setType(Material.AIR);
}
// Manuell aus den Datenstrukturen entfernen
it.remove();
chestSpawnTimes.remove(loc);
it.remove(); // Aus der Liste entfernen (Thread-Safe)
Bukkit.broadcastMessage(color(
plugin.getLangConfig().getString("lootchest.despawn-msg",
"&eEine Lootkiste ist von selbst verschwunden.")
));
}
}
// Config einmal speichern (wichtig!)
saveActiveChests();
}
}.runTaskTimer(plugin, 20L * 60, 20L * 60);
}
@@ -238,7 +117,6 @@ public class LootChestManager implements Listener, CommandExecutor {
if (loc.getBlock().getType() == Material.CHEST) {
loc.getBlock().setType(Material.AIR);
}
removeChestData(loc);
}
activeChests.clear();
chestSpawnTimes.clear();
@@ -280,8 +158,6 @@ public class LootChestManager implements Listener, CommandExecutor {
activeChests.add(loc);
chestSpawnTimes.put(loc, System.currentTimeMillis());
saveActiveChests();
world.spawnParticle(Particle.FIREWORK, loc.clone().add(0.5, 1.0, 0.5),
20, 0.3, 0.5, 0.3);
world.playSound(loc, Sound.BLOCK_CHEST_OPEN, 1.0f, 1.0f);
@@ -389,9 +265,8 @@ public class LootChestManager implements Listener, CommandExecutor {
if (looted >= maxLootPerPlayer) return;
playerLootCount.put(uuid, looted + 1);
chestLoc.getBlock().setType(Material.AIR);
removeChestData(chestLoc);
activeChests.remove(chestLoc);
chestSpawnTimes.remove(chestLoc);
Bukkit.broadcastMessage(color(
plugin.getLangConfig().getString("lootchest.removed-msg", "&aEine Lootkiste wurde geleert und entfernt.")
));

View File

@@ -28,51 +28,24 @@ public class ShopManager {
return shopConfig.getDouble("items." + itemKey + ".current-price", 0);
}
public int getStock(String itemKey) {
return shopConfig.getInt("items." + itemKey + ".stock", 0);
}
/**
* Spieler kauft Item: Lager sinkt, Preis steigt (Verknappung)
*/
public boolean buyItem(String itemKey, int amount) {
int stock = shopConfig.getInt("items." + itemKey + ".stock", 0);
if (stock < amount) {
return false;
}
double price = shopConfig.getDouble("items." + itemKey + ".current-price", 0);
// Lager aktualisieren
shopConfig.set("items." + itemKey + ".stock", stock - amount);
// Preis erhöhen (Faktor 1.05 = +5%)
double newPrice = price * 1.05;
shopConfig.set("items." + itemKey + ".current-price", newPrice);
shopConfig.set("items." + itemKey + ".current-price", price * 0.95);
saveShop();
return true;
}
/**
* Spieler verkauft Item: Lager steigt, Preis sinkt (Überfluss)
*/
public boolean sellItem(String itemKey, int amount) {
// Hier könnte man prüfen, ob der Shop genügend Geld hat (falls nötig)
double price = shopConfig.getDouble("items." + itemKey + ".current-price", 0);
public void sellItem(String itemKey, int amount) {
int stock = shopConfig.getInt("items." + itemKey + ".stock", 0);
// Lager aktualisieren
double price = shopConfig.getDouble("items." + itemKey + ".current-price", 0);
shopConfig.set("items." + itemKey + ".stock", stock + amount);
// Preis senken (Faktor 0.95 = -5%)
double newPrice = price * 0.95;
shopConfig.set("items." + itemKey + ".current-price", newPrice);
shopConfig.set("items." + itemKey + ".current-price", price * 0.95);
saveShop();
return true;
}
public void addOrUpdateItem(String itemKey, double basePrice, int stock) {

View File

@@ -1,30 +1,30 @@
package de.viper.survivalplus.Manager;
import de.viper.survivalplus.SurvivalPlus;
import me.clip.placeholderapi.PlaceholderAPI;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.ScoreboardManager;
import org.bukkit.scoreboard.Team;
import me.clip.placeholderapi.PlaceholderAPI;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import java.io.File;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TablistManager implements Listener {
@@ -44,33 +44,39 @@ public class TablistManager implements Listener {
private String separatorLine;
private LuckPerms luckPerms;
private boolean hasPlaceholderAPI;
private FileConfiguration nicknameConfig;
private final Scoreboard scoreboard;
private final Map<String, Team> prefixTeams = new HashMap<>();
private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}");
private final Map<String, Team> prefixTeams;
private FileConfiguration nicknameConfig;
public TablistManager(SurvivalPlus plugin) {
this.plugin = plugin;
this.prefixTeams = new HashMap<>();
// Scoreboard initialisieren
ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
this.scoreboard = scoreboardManager != null ? scoreboardManager.getMainScoreboard() : null;
// Resource sicherstellen, Config laden
try {
File tablistFile = new File(plugin.getDataFolder(), "tablist.yml");
if (!tablistFile.exists()) {
plugin.saveResource("tablist.yml", false);
}
plugin.saveResource("tablist.yml", false);
} catch (Exception ignored) {}
try {
plugin.reloadTablistConfig();
} catch (Exception ignored) {}
FileConfiguration config = plugin.getTablistConfig();
// Nicknames.yml laden
try {
File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml");
if (!nicknameFile.exists()) {
plugin.saveResource("nicknames.yml", false);
}
this.nicknameConfig = YamlConfiguration.loadConfiguration(nicknameFile);
} catch (Exception ignored) {
this.nicknameConfig = null; // Keine Konsolenausgabe
}
// Konfigurationswerte laden
this.enabled = config.getBoolean("enabled", true);
this.serverName = config.getString("server-name", "SurvivalPlus");
this.website = config.getString("website", "www.example.com");
@@ -81,22 +87,27 @@ public class TablistManager implements Listener {
this.staffPermission = config.getString("staff-permission", "survivalplus.staff");
this.separatorLine = config.getString("separator-line", "&8&l&m================");
// LuckPerms API initialisieren
try {
this.luckPerms = LuckPermsProvider.get();
} catch (Throwable e) {
luckPerms = null;
luckPerms = null; // Keine Konsolenausgabe
}
// Prüfen, ob PlaceholderAPI verfügbar ist
this.hasPlaceholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
// Registriere Chat-Listener hier, damit Prefix im Chat gesetzt wird
Bukkit.getPluginManager().registerEvents(this, plugin);
// Chat-Listener registrieren
if (enabled) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
if (!enabled) {
plugin.getLogger().info("Tablist ist deaktiviert (tablist.yml -> enabled: false)");
return;
}
// Header- und Footer-Animationen füllen
List<String> configHeader = config.getStringList("header-animations");
List<String> configFooter = config.getStringList("footer-animations");
@@ -123,49 +134,40 @@ public class TablistManager implements Listener {
new BukkitRunnable() {
@Override
public void run() {
reloadNicknameConfig();
if (headerAnim.isEmpty() || footerAnim.isEmpty()) return;
// Online-Spieler und Staff zählen
int onlinePlayers = Bukkit.getOnlinePlayers().size();
int onlineStaff = (int) Bukkit.getOnlinePlayers().stream()
.filter(p -> p.hasPermission(staffPermission))
.count();
// Aktuelles Datum und Uhrzeit formatieren
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
String currentTime = dateFormat.format(new Date());
for (Player player : Bukkit.getOnlinePlayers()) {
try {
// ------ Name / Nick handling exactly like in your old code ------
// Nickname oder Spielername verwenden
String displayName = getNickname(player);
if (displayName == null || displayName.trim().isEmpty()) {
displayName = player.getName();
}
String rankPrefix = getPlayerPrefix(player);
// Update nametag (prefix above head) as in old code:
updateNametag(player, rankPrefix, getNickname(player)); // pass raw nickname (colored) if present
// PlayerList (TAB)
int ping = getPlayerPing(player);
// Spielername für die Tablist setzen
String playerListName;
String prefix = getPlayerPrefix(player);
if (luckPerms == null && !hasPlaceholderAPI) {
playerListName = displayName;
updateNametag(player, "", displayName);
} else {
playerListName = color(rankPrefix + displayName + (ping >= 0 ? "&8 | &e" + ping + "ms" : ""));
int ping = getPlayerPing(player);
playerListName = color(prefix + displayName + (ping >= 0 ? "&8 | &e" + ping + "ms" : ""));
updateNametag(player, prefix, displayName);
}
player.setPlayerListName(playerListName);
try {
player.setPlayerListName(playerListName);
} catch (Exception ignored) {
// safe fallback: set without prefix
try {
player.setPlayerListName(color(displayName));
} catch (Exception ignored2) {}
}
// Header/Footer build (including TS/Discord/Website and time)
// Header mit Spielername/Nickname und Statistiken
String headerRaw = headerAnim.get(headerIndex)
.replace("{server}", serverName)
.replace("{player}", displayName)
@@ -173,9 +175,11 @@ public class TablistManager implements Listener {
.replace("{staff}", String.valueOf(onlineStaff));
String footerRaw = footerAnim.get(footerIndex);
// Footer zusammenstellen: TS und Discord nebeneinander, Webseite zentriert
StringBuilder footerBuilder = new StringBuilder();
footerBuilder.append("\n");
footerBuilder.append("\n"); // Extra Abstand
footerBuilder.append(color(footerRaw)).append("\n");
footerBuilder.append(color(separatorLine)).append("\n");
if (showTeamspeak || showDiscord) {
StringBuilder socialLine = new StringBuilder();
@@ -197,40 +201,28 @@ public class TablistManager implements Listener {
String header = color(headerRaw);
String footer = color(footerBuilder.toString());
// Try Adventure-based header/footer first, fallback to String reflection
// 1) Adventure (Component) Variante
boolean done = tryAdventureComponent(player, headerRaw, footerBuilder.toString());
// 2) String-Variante fallback
if (!done) {
tryStringMethod(player, header, footer);
done = tryStringMethod(player, header, footer);
}
} catch (Exception ex) {
plugin.getLogger().warning("TablistManager error: " + ex.getMessage());
// 3) Keine Warnung bei Fehlschlag, da dies normal sein kann
} catch (Exception ignored) {
// Keine Konsolenausgabe für Fehler bei der Tablist
}
}
headerIndex = (headerIndex + 1) % headerAnim.size();
footerIndex = (footerIndex + 1) % footerAnim.size();
}
}.runTaskTimer(plugin, 40L, interval);
}.runTaskTimer(plugin, 0L, interval);
}
/**
* Lädt nicknames.yml neu.
*/
private void reloadNicknameConfig() {
try {
File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml");
if (!nicknameFile.exists()) {
plugin.saveResource("nicknames.yml", false);
}
this.nicknameConfig = YamlConfiguration.loadConfiguration(nicknameFile);
} catch (Exception ignored) {
this.nicknameConfig = null;
}
}
/**
* Nickname abrufen und formatieren (farbig + hex)
* Nickname aus nicknames.yml abrufen
*/
private String getNickname(Player player) {
if (nicknameConfig == null) return null;
@@ -238,104 +230,199 @@ public class TablistManager implements Listener {
String uuid = player.getUniqueId().toString();
String nickname = nicknameConfig.getString(uuid);
if (nickname != null && !nickname.trim().isEmpty()) {
return translateNickColors(nickname);
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".nickname", nickname);
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return nickname;
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
return null;
}
/**
* Übersetzt &-Codes und Hex-Farben
* Nametag über dem Kopf aktualisieren
*/
private String translateNickColors(String input) {
String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input);
Matcher matcher = HEX_PATTERN.matcher(withLegacy);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String hexCode = matcher.group();
String replacement = ChatColor.of(hexCode).toString();
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* Aktualisiert den Nametag über dem Kopf genau wie in deinem alten Code:
* - Wenn Nick vorhanden: Nick als Prefix (zeigt [Nick] Spielername)
* - Sonst Rang-Prefix (oder [Spieler] wenn kein Rang)
* Der Entry ist dabei immer der echte Spielername.
*/
private void updateNametag(Player player, String rankPrefix, String nickname) {
private void updateNametag(Player player, String prefix, String displayName) {
if (scoreboard == null) return;
try {
String teamName = generateTeamName(player, rankPrefix);
String teamName = generateTeamName(player, prefix);
Team team = scoreboard.getTeam(teamName);
// Team erstellen oder aktualisieren
if (team == null) {
// register new team if not exists
try {
team = scoreboard.registerNewTeam(teamName);
} catch (IllegalArgumentException ignored) {
team = scoreboard.getTeam(teamName);
}
}
if (team == null) return;
String prefix;
if (nickname != null && !nickname.trim().isEmpty()) {
prefix = nickname;
} else {
String rp = (rankPrefix != null && !rankPrefix.trim().isEmpty()) ? rankPrefix : "&7[&aSpieler&7]&f ";
prefix = color(rp);
team = scoreboard.registerNewTeam(teamName);
}
try {
team.setPrefix(prefix);
} catch (Exception ignored) {}
// Ensure the genuine player name is used as the team entry
String entry = player.getName();
// Prefix zwingend setzen, wenn LuckPerms installiert ist
String coloredPrefix = color(prefix != null && !prefix.trim().isEmpty() ? prefix : (luckPerms != null ? "&7[Spieler] " : ""));
team.setPrefix(coloredPrefix);
// Spieler dem Team hinzufügen
String entry = displayName != null && !displayName.trim().isEmpty() ? displayName : player.getName();
if (!team.hasEntry(entry)) {
// Remove from other teams to avoid duplicates
// Spieler aus anderen Teams entfernen, um Konflikte zu vermeiden
for (Team existingTeam : scoreboard.getTeams()) {
if (existingTeam != null && !existingTeam.getName().equals(teamName)) {
try {
existingTeam.removeEntry(entry);
} catch (Exception ignored) {}
if (!existingTeam.getName().equals(teamName)) {
existingTeam.removeEntry(entry);
existingTeam.removeEntry(player.getName());
}
}
try {
team.addEntry(entry);
} catch (Exception ignored) {}
team.addEntry(entry);
}
// Synchronize scoreboard to online players (use main scoreboard)
// Team für alle Spieler sichtbar machen
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
try {
if (onlinePlayer.getScoreboard() != scoreboard) {
onlinePlayer.setScoreboard(scoreboard);
}
} catch (Exception ignored) {}
if (onlinePlayer.getScoreboard() != scoreboard) {
onlinePlayer.setScoreboard(scoreboard);
}
}
} catch (Exception ignored) {}
// Debug-Ausgabe für Nametag
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".nametag_prefix", coloredPrefix);
debugConfig.set(player.getUniqueId().toString() + ".nametag_entry", entry);
debugConfig.save(debugFile);
} catch (Exception ignored) {}
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
}
/**
* Eindeutigen Teamnamen generieren
*/
private String generateTeamName(Player player, String prefix) {
// Verwende UUID für eindeutige Teamnamen, falls kein Prefix vorhanden
if (prefix == null || prefix.trim().isEmpty()) {
return "nametag_" + player.getUniqueId().toString().substring(0, 8);
}
// Verwende Prefix für Teamnamen, max 16 Zeichen (Bukkit-Beschränkung)
String sanitizedPrefix = prefix.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
return "prefix_" + sanitizedPrefix.substring(0, Math.min(sanitizedPrefix.length(), 12));
}
/**
* Versucht, Adventure Components zu verwenden (falls verfügbar).
* Spieler-Prefix abrufen (LuckPerms primär, PlaceholderAPI als Fallback)
*/
private String getPlayerPrefix(Player player) {
// Wenn LuckPerms installiert ist, Prefix zwingend abrufen
if (luckPerms != null) {
// Versuche LuckPerms-API zuerst
try {
User user = luckPerms.getPlayerAdapter(Player.class).getUser(player);
String prefix = user.getCachedData().getMetaData().getPrefix();
if (prefix != null && !prefix.trim().isEmpty()) {
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix);
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return prefix + " ";
}
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
// Fallback auf PlaceholderAPI, wenn LuckPerms installiert ist
if (hasPlaceholderAPI) {
try {
String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%");
if (prefix != null && !prefix.trim().isEmpty()) {
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix);
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return prefix + " ";
}
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
}
// Standard-Prefix, wenn LuckPerms installiert ist, aber kein Prefix definiert
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".prefix", "&7[Spieler]");
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return "&7[Spieler] ";
}
// Wenn LuckPerms nicht installiert ist, aber PlaceholderAPI vorhanden ist
if (hasPlaceholderAPI) {
try {
String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%");
if (prefix != null && !prefix.trim().isEmpty()) {
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix);
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return prefix + " ";
}
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
}
// Standard-Prefix, wenn weder LuckPerms noch PlaceholderAPI einen Prefix liefern
try {
File debugFile = new File(plugin.getDataFolder(), "debug.yml");
FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile);
debugConfig.set(player.getUniqueId().toString() + ".prefix", "&7[Spieler]");
debugConfig.save(debugFile);
} catch (Exception ignored) {}
return "&7[Spieler] ";
}
/**
* Spieler-Ping abrufen
*/
private int getPlayerPing(Player player) {
try {
Method getHandle = player.getClass().getMethod("getHandle");
Object entityPlayer = getHandle.invoke(player);
return entityPlayer.getClass().getField("ping").getInt(entityPlayer);
} catch (Exception ignored) {
return -1; // Keine Konsolenausgabe
}
}
/**
* Chat-Format modifizieren
*/
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
try {
Player player = event.getPlayer();
String displayName = getNickname(player);
if (displayName == null || displayName.trim().isEmpty()) {
displayName = player.getName();
}
String prefix = getPlayerPrefix(player);
String format = color(prefix + displayName + "&7: &f") + "%2$s";
event.setFormat(format);
} catch (Exception ignored) {
// Keine Konsolenausgabe
}
}
/**
* Adventure-Variante mit Components
*/
private boolean tryAdventureComponent(Player player, String headerRaw, String footerRaw) {
try {
@@ -356,6 +443,7 @@ public class TablistManager implements Listener {
headerComp = textMethod.invoke(null, color(headerRaw));
footerComp = textMethod.invoke(null, color(footerRaw));
} else {
// Fallback: MiniMessage
try {
Class<?> miniMsgClass = Class.forName("net.kyori.adventure.text.minimessage.MiniMessage");
Method miniMsgSingleton = miniMsgClass.getMethod("miniMessage");
@@ -363,7 +451,9 @@ public class TablistManager implements Listener {
Method deserialize = miniMsgClass.getMethod("deserialize", String.class);
headerComp = deserialize.invoke(miniMsg, headerRaw);
footerComp = deserialize.invoke(miniMsg, footerRaw);
} catch (Exception ignored) {}
} catch (Exception ignored) {
// Kein MiniMessage
}
}
if (headerComp == null || footerComp == null) return false;
@@ -384,13 +474,13 @@ public class TablistManager implements Listener {
}
}
} catch (Exception ignored) {
// fallback to string method
return false;
}
return false;
}
/**
* Fallback: konventionelle String-Header/Footer mittels Reflection
* String-Variante
*/
private boolean tryStringMethod(Player player, String header, String footer) {
try {
@@ -410,10 +500,15 @@ public class TablistManager implements Listener {
mf.invoke(player, footer);
return true;
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
return false;
}
return false;
}
/**
* Hilfsmethode für Reflection
*/
private Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
Class<?> current = clazz;
while (current != null) {
@@ -427,67 +522,8 @@ public class TablistManager implements Listener {
}
/**
* Holt den Rang-Prefix (LuckPerms -> PlaceholderAPI -> Default [Spieler])
* Farbcode-Konvertierung (& -> §)
*/
private String getPlayerPrefix(Player player) {
if (luckPerms != null) {
try {
User user = luckPerms.getPlayerAdapter(Player.class).getUser(player);
String prefix = user.getCachedData().getMetaData().getPrefix();
if (prefix != null && !prefix.trim().isEmpty()) {
return prefix + " ";
}
} catch (Exception ignored) {}
if (hasPlaceholderAPI) {
try {
String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%");
if (prefix != null && !prefix.trim().isEmpty()) {
return prefix + " ";
}
} catch (Exception ignored) {}
}
return "&7[&aSpieler&7]&f ";
}
if (hasPlaceholderAPI) {
try {
String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%");
if (prefix != null && !prefix.trim().isEmpty()) {
return prefix + " ";
}
} catch (Exception ignored) {}
}
return "&7[&aSpieler&7]&f ";
}
private int getPlayerPing(Player player) {
try {
Method getHandle = player.getClass().getMethod("getHandle");
Object entityPlayer = getHandle.invoke(player);
return entityPlayer.getClass().getField("ping").getInt(entityPlayer);
} catch (Exception ignored) {
return -1;
}
}
/**
* Chat-Format (Nickname wird hier angezeigt). Setzt Prefix + Nick/Name im Chat.
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerChat(AsyncPlayerChatEvent event) {
try {
Player player = event.getPlayer();
String displayName = getNickname(player);
if (displayName == null || displayName.trim().isEmpty()) {
displayName = player.getName();
}
String prefix = getPlayerPrefix(player);
// Format: Rang + Nick + ": " + Nachricht
String format = color(prefix + displayName + "&7: &f") + "%2$s";
event.setFormat(format);
} catch (Exception ignored) {}
}
private String color(String msg) {
if (msg == null) return "";
return msg.replace("&", "§");

View File

@@ -66,13 +66,6 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
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 {
@@ -92,13 +85,6 @@ public class SurvivalPlus extends JavaPlugin {
private FileConfiguration leashesConfig;
private File mobCapFile;
private FileConfiguration mobCapConfig;
// --- NEU: Adaptive Mob System ---
private File mobAdaptFile;
private FileConfiguration mobAdaptConfig;
private AdaptiveMobListener adaptiveMobListener;
// --------------------------------
private FileConfiguration tablistConfig;
private File tablistFile;
private File nicknamesFile;
@@ -130,11 +116,6 @@ public class SurvivalPlus extends JavaPlugin {
private Map<UUID, Integer> playerClaimCounts = new HashMap<>();
private Map<UUID, Location> point1Selections = 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() {
if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml");
@@ -187,41 +168,6 @@ public class SurvivalPlus extends JavaPlugin {
@Override
public void onEnable() {
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;
Metrics metrics = new Metrics(this, pluginId);
metrics.addCustomChart(new SimplePie(
@@ -236,10 +182,6 @@ public class SurvivalPlus extends JavaPlugin {
"plugin_language",
() -> getConfig().getString("language", "default")
));
bannerManager = new BannerManager(this);
bannerManager.displayConsoleBanner(); // Banner beim Serverstart
updateConfigFiles();
createHomesFile();
createGravesFile();
@@ -247,7 +189,6 @@ public class SurvivalPlus extends JavaPlugin {
createFriendsFile();
createLeashesFile();
createMobCapFile();
createMobAdaptFile(); // NEU: Adaptive Mob File erstellen
createNicknamesFile();
reloadBlockedCommandsConfig();
loadClaims();
@@ -342,18 +283,10 @@ public class SurvivalPlus extends JavaPlugin {
pluginManager.registerEvents(lootChestManager, this);
getCommand("lootchests").setExecutor(lootChestManager);
getCommand("tploot").setExecutor(lootChestManager);
getCommand("ride").setExecutor(new RideCommand(this));
getCommand("claim").setExecutor(new ClaimCommand(this));
// HIER: (this) hinzugefügt, damit der BlockManager die Datei speichern kann
BlockManager blockManager = new BlockManager(this);
BlockManager blockManager = new BlockManager();
FileConfiguration config = getConfig();
BackpackRecipe.register(this, langConfig);
// NEU: Befehle registrieren
getCommand("block").setExecutor(new BlockCommand(blockManager, getConfig()));
getCommand("unblock").setExecutor(new UnblockCommand(blockManager, getConfig()));
getCommand("blocklist").setExecutor(new BlockListCommand(blockManager, getConfig()));
pluginManager.registerEvents(new ChatBlockListener(blockManager), this);
pluginManager.registerEvents(new InventoryClickListener(this), this);
pluginManager.registerEvents(sitListener, this);
@@ -376,14 +309,7 @@ public class SurvivalPlus extends JavaPlugin {
pluginManager.registerEvents(new WarpInventoryListener(this, warpManager), this);
pluginManager.registerEvents(new ChallengeSmeltListener(funChallengeManager), this);
pluginManager.registerEvents(new BlockDetectionListener(this), this);
pluginManager.registerEvents(new NickLoadListener(this), this);
pluginManager.registerEvents(new ClaimListener(this), this);
// --- NEU: Adaptive Mob Listener registrieren ---
adaptiveMobListener = new AdaptiveMobListener(this);
pluginManager.registerEvents(adaptiveMobListener, this);
// -----------------------------------------
lockSystem = new LockSystem(this);
pluginManager.registerEvents(lockSystem, this);
getCommand("sp").setExecutor(lockSystem);
@@ -408,6 +334,7 @@ public class SurvivalPlus extends JavaPlugin {
});
pluginManager.registerEvents(new RepairSignListener(getConfig(), getLangConfig()), this);
pluginManager.registerEvents(new ToolUpgradeListener(this), this);
pluginManager.registerEvents(new NickLoadListener(this), this);
startAutoClearTask();
spawnArmorStandExample();
getLogger().info(getMessage("plugin.enabled"));
@@ -456,77 +383,6 @@ public class SurvivalPlus extends JavaPlugin {
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) {
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("survivalplus.notify")) {
@@ -702,6 +558,19 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver
// Methoden für nicknames.yml
public void createNicknamesFile() {
nicknamesFile = new File(getDataFolder(), "nicknames.yml");
@@ -749,16 +618,12 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver
@Override
public void onDisable() {
if (debugWriter != null) {
debugWriter.close();
}
if (autoClearTaskId != -1) {
Bukkit.getScheduler().cancelTask(autoClearTaskId);
}
saveStats();
saveLeashesConfig();
saveMobCapConfig();
saveMobAdaptConfig(); // NEU: Adaptive Mob Config speichern
// NEU: NewbieProtection-Daten sichern
if (newbieListener != null) {
@@ -1105,33 +970,6 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver
getLogger().info("mobcap.yml erfolgreich neu geladen.");
}
// === NEU: MobAdapt.yml ===
private void createMobAdaptFile() {
mobAdaptFile = new File(getDataFolder(), "mobadapt.yml");
if (!mobAdaptFile.exists()) {
try {
mobAdaptFile.createNewFile();
getLogger().info("mobadapt.yml wurde erstellt.");
} catch (IOException e) {
getLogger().severe("Fehler beim Erstellen der mobadapt.yml: " + e.getMessage());
}
}
mobAdaptConfig = YamlConfiguration.loadConfiguration(mobAdaptFile);
}
public FileConfiguration getMobAdaptConfig() {
return mobAdaptConfig;
}
public void saveMobAdaptConfig() {
try {
mobAdaptConfig.save(mobAdaptFile);
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Fehler beim Speichern der mobadapt.yml: " + e.getMessage());
}
}
// ===========================
// === AutoClearTask ===
private void startAutoClearTask() {
if (autoClearTaskId != -1) {
@@ -1228,34 +1066,4 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver
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
}
}

View File

@@ -2,12 +2,9 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.Manager.BlockManager;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.configuration.file.FileConfiguration;
public class BlockCommand implements CommandExecutor {
@@ -23,40 +20,28 @@ public class BlockCommand implements CommandExecutor {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(color(config.getString("messages.general.only_players", "§cDieser Befehl ist nur für Spieler!")));
return true;
}
if (!player.hasPermission("survivalplus.block")) {
player.sendMessage(color(config.getString("messages.general.no_permission", "§cDu hast keine Berechtigung für diesen Befehl!")));
sender.sendMessage(config.getString("messages.general.only_players"));
return true;
}
if (args.length != 1) {
player.sendMessage(color(config.getString("messages.block.usage", "§cVerwendung: /block <Spieler>")));
player.sendMessage(config.getString("messages.block.usage"));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null || target == player) {
player.sendMessage(color(config.getString("messages.block.invalid_player", "§cDieser Spieler konnte nicht gefunden werden oder bist du selbst.")));
player.sendMessage(config.getString("messages.block.invalid_player"));
return true;
}
if (blockManager.hasBlocked(player, target)) {
String msg = config.getString("messages.block.already_blocked", "&cDieser Spieler ist bereits blockiert!");
player.sendMessage(color(msg.replace("%player%", target.getName())));
player.sendMessage(config.getString("messages.block.already_blocked").replace("%player%", target.getName()));
} else {
blockManager.blockPlayer(player, target);
String msg = config.getString("messages.block.blocked", "&aDu hast %player% blockiert.");
player.sendMessage(color(msg.replace("%player%", target.getName())));
player.sendMessage(config.getString("messages.block.blocked").replace("%player%", target.getName()));
}
return true;
}
private String color(String msg) {
if (msg == null) return "";
return ChatColor.translateAlternateColorCodes('&', msg);
}
}

View File

@@ -2,12 +2,9 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.Manager.BlockManager;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.stream.Collectors;
@@ -25,30 +22,22 @@ public class BlockListCommand implements CommandExecutor {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
// Fallback: "Nur Spieler..." falls Key fehlt
sender.sendMessage(color(config.getString("messages.general.only_players", "§cNur Spieler können diesen Befehl ausführen!")));
sender.sendMessage(config.getString("messages.general.only_players"));
return true;
}
var blocked = blockManager.getBlockedPlayers(player);
if (blocked.isEmpty()) {
player.sendMessage(color(config.getString("messages.blocklist.no_blocked_players", "§cDu hast keine Spieler blockiert.")));
player.sendMessage(config.getString("messages.blocklist.no_blocked_players"));
return true;
}
String list = blocked.stream()
.map(Bukkit::getOfflinePlayer)
.map(p -> p.getName() != null ? p.getName() : "§eUnbekannt")
.map(p -> p.getName() != null ? p.getName() : "Unbekannt")
.collect(Collectors.joining(", "));
// WICHTIG: Default-String als zweiten Parameter, um NullPointerException zu vermeiden
String rawMessage = config.getString("messages.blocklist.blocked_players", "&eBlockierte Spieler: %list%");
player.sendMessage(color(rawMessage.replace("%list%", list)));
player.sendMessage(config.getString("messages.blocklist.blocked_players").replace("%list%", list));
return true;
}
private String color(String msg) {
if (msg == null) return "";
return ChatColor.translateAlternateColorCodes('&', msg);
}
}

View File

@@ -116,14 +116,14 @@ public class FriendCommand implements CommandExecutor {
}
private void sendHelpMessage(Player player) {
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.header", "&6== Freundesliste Hilfe ==")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.header", "&6=== Freundesliste Hilfe ===")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.add", "&e/friend add <Spielername> &7- Freundschaftsanfrage senden")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.accept", "&e/friend accept <Spielername> &7- Freundschaftsanfrage akzeptieren")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.deny", "&e/friend deny <Spielername> &7- Freundschaftsanfrage ablehnen")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.list", "&e/friend list &7- Liste deiner Freunde anzeigen")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.del", "&e/friend del <Spielername> &7- Freund aus der Liste entfernen")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.tp", "&e/friend tp <Spielername> &7- Zu einem Freund teleportieren")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.footer", "&6=======================")));
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.footer", "&6=====================")));
}
private void handleFriendAdd(Player player, String targetName) {
@@ -155,23 +155,15 @@ public class FriendCommand implements CommandExecutor {
pendingRequests.add(playerUUID.toString());
friendsConfig.set(targetUUID + ".pending_requests", pendingRequests);
friendsConfig.set(targetUUID + ".name", targetName);
// --- FIX START: Absenderdaten speichern, damit er auch offline gefunden werden kann ---
friendsConfig.set(playerUUID + ".name", player.getName());
friendsConfig.set(playerUUID + ".last-online", System.currentTimeMillis());
// --- FIX END ---
saveFriendsConfig();
player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.sent", "&aFreundschaftsanfrage an %s gesendet!").replace("%s", targetName)));
TextComponent message = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.received", "&aDu hast eine Freundschaftsanfrage von %s erhalten! ").replace("%s", player.getName())));
TextComponent accept = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.accept-button", "&a[Accept]")));
// ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND
accept.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend accept " + player.getName()));
accept.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend accept " + player.getName()));
TextComponent deny = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.deny-button", "&c [Deny]")));
// ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND
deny.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend deny " + player.getName()));
deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend deny " + player.getName()));
message.addExtra(accept);
message.addExtra(deny);
target.spigot().sendMessage(message);
@@ -276,8 +268,7 @@ public class FriendCommand implements CommandExecutor {
}
TextComponent removeButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.list.remove-button", "&c[X]")));
// ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND
removeButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend del " + friendName));
removeButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend del " + friendName));
entry.addExtra(" ");
entry.addExtra(removeButton);
@@ -305,11 +296,9 @@ public class FriendCommand implements CommandExecutor {
TextComponent confirmMessage = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.confirm", "&cMöchtest du %s wirklich aus deiner Freundesliste entfernen? ").replace("%s", friendName)));
TextComponent confirmButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.confirm-button", "&a[Confirm]")));
// ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND
confirmButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend confirm " + friendName));
confirmButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend confirm " + friendName));
TextComponent cancelButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.cancel-button", "&c[Cancel]")));
// ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND
cancelButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend list"));
cancelButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend list"));
confirmMessage.addExtra(confirmButton);
confirmMessage.addExtra(" ");
confirmMessage.addExtra(cancelButton);

View File

@@ -34,25 +34,10 @@ public class NickCommand implements CommandExecutor {
if (args.length == 0) {
player.sendMessage("§eBenutzung: /nick <Name>");
player.sendMessage("§eVerwende /nick off um den Nickname zu entfernen.");
return true;
}
// Entfernen des Nicks
if (args.length == 1 && (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("remove") || args[0].equalsIgnoreCase("reset"))) {
String uuid = player.getUniqueId().toString();
plugin.getNicknamesConfig().set(uuid, null);
plugin.saveNicknamesConfig();
// WICHTIG: Nur DisplayName setzen, TablistManager übernimmt den Rest
player.setDisplayName(player.getName());
player.sendMessage("§aDein Nickname wurde entfernt.");
return true;
}
// Zusammenbauen des Nicknames
// Name zusammensetzen
StringBuilder sb = new StringBuilder();
for (String arg : args) {
sb.append(arg).append(" ");
@@ -71,24 +56,28 @@ public class NickCommand implements CommandExecutor {
// Farben anwenden
String coloredNick = translateColors(rawNick);
// Chat/Display: [Nick]
String finalNickForDisplay = "§f[" + coloredNick + "§f]";
// Format: [Nickname] (Klammern weiß, Nickname farbig)
String finalNick = "§f[" + coloredNick + "§f]";
// WICHTIG: Nur DisplayName setzen
// playerListName wird vom TablistManager automatisch gesetzt
player.setDisplayName(finalNickForDisplay);
// Anwenden
player.setDisplayName(finalNick);
player.setPlayerListName(finalNick);
// Speichern (ungefärbten Namen)
plugin.getNicknamesConfig().set(player.getUniqueId().toString(), rawNick);
plugin.saveNicknamesConfig();
player.sendMessage("§aDein Nickname wurde zu " + finalNickForDisplay + " geändert.");
player.sendMessage("§aDein Nickname wurde zu " + finalNick + " geändert.");
return true;
}
private String translateColors(String input) {
String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input);
Matcher matcher = HEX_PATTERN.matcher(withLegacy);
return replaceHexColors(withLegacy);
}
private String replaceHexColors(String input) {
Matcher matcher = HEX_PATTERN.matcher(input);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String hexCode = matcher.group();

View File

@@ -1,100 +0,0 @@
package de.viper.survivalplus.commands;
import de.viper.survivalplus.SurvivalPlus;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.util.RayTraceResult;
public class RideCommand implements CommandExecutor {
private final SurvivalPlus plugin;
public RideCommand(SurvivalPlus plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("Dieser Befehl ist nur für Spieler!");
return true;
}
Player player = (Player) sender;
FileConfiguration lang = plugin.getLangConfig();
if (!player.hasPermission("survivalplus.ride")) {
player.sendMessage(ChatColor.RED + "Du hast keine Berechtigung dafür!");
return true;
}
// --- FALL 1: ABSTEIGEN ---
if (args.length == 0) {
if (player.isInsideVehicle()) {
player.leaveVehicle();
player.sendMessage(ChatColor.GREEN + "Du bist abgestiegen!");
return true;
}
// --- FALL 2: AUFSTEIGEN (Via Raycast) ---
double maxDistance = 5.0;
// FIX: Wir übergeben einen Filter, der den Spieler selbst ignoriert
RayTraceResult rayTrace = player.getWorld().rayTraceEntities(
player.getEyeLocation(),
player.getEyeLocation().getDirection(),
maxDistance,
1.0, // Ray Größe (Dicke des Strahls)
entity -> !entity.getUniqueId().equals(player.getUniqueId()) // WICHTIG: Ignoriere mich selbst
);
if (rayTrace == null || rayTrace.getHitEntity() == null) {
player.sendMessage(ChatColor.RED + "Du schaust auf nichts zum Reiten!");
player.sendMessage(ChatColor.GRAY + "Schaue einen Spieler oder Mob an.");
return true;
}
Entity target = rayTrace.getHitEntity();
// Verhindern, dass man aufgesammelte Items reitet
if (target instanceof Item) {
player.sendMessage(ChatColor.RED + "Du kannst keine aufgesammelten Items reiten!");
return true;
}
// --- Zielle Logik ---
if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (targetPlayer.equals(player)) {
player.sendMessage(ChatColor.RED + "Du kannst dich nicht selbst reiten!");
return true;
}
if (targetPlayer.hasPermission("survivalplus.ride.exempt")) {
player.sendMessage(ChatColor.RED + "Du kannst diesen Spieler nicht reiten!");
return true;
}
target.addPassenger(player);
player.sendMessage(ChatColor.GREEN + "Du reitest nun " + ChatColor.YELLOW + targetPlayer.getName() + ChatColor.GREEN + "!");
targetPlayer.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.GREEN + " reitet dich jetzt!");
} else {
target.addPassenger(player);
player.sendMessage(ChatColor.GREEN + "Du reitest nun ein(e) " + target.getName() + "!");
}
return true;
}
player.sendMessage(ChatColor.GRAY + "Benutzung: Schaue auf ein Wesen und tippe " + ChatColor.WHITE + "/ride");
return true;
}
}

View File

@@ -2,7 +2,7 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.Manager.ShopManager;
import de.viper.survivalplus.SurvivalPlus;
import de.viper.survivalplus.gui.ShopGui; // <--- WICHTIG: Dieser Import fehlte dir
import de.viper.survivalplus.gui.ShopGui;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@@ -34,7 +34,7 @@ public class ShopCommand implements CommandExecutor {
}
Player player = (Player) sender;
// Shop-GUI öffnen
// Shop-GUI öffnen, wenn kein Argument oder "gui"
if (args.length == 0 || (args.length > 0 && args[0].toLowerCase().equals("gui"))) {
ShopGui shopGui = new ShopGui(plugin, player, shopManager);
Bukkit.getPluginManager().registerEvents(shopGui, plugin);
@@ -42,69 +42,41 @@ public class ShopCommand implements CommandExecutor {
return true;
}
if (args.length < 3) {
sender.sendMessage(getMessage("usage-add"));
return false;
}
if (!args[0].toLowerCase().equals("add")) {
sender.sendMessage(getMessage("unknown-subcommand"));
return false;
}
ItemStack itemInHand = player.getInventory().getItemInMainHand();
if (itemInHand == null || itemInHand.getType() == Material.AIR) {
sender.sendMessage(ChatColor.RED + "Du musst das Item, das du hinzufügen willst, in der Hand halten!");
return true;
}
// --- Neue Logik für /shop add ---
String itemKey = itemInHand.getType().name().toLowerCase();
String itemKey;
double basePrice;
int stock;
// Fall 1: /shop add <Material> <Preis> <Stock> (4 Argumente)
if (args.length >= 4) {
String materialName = args[1];
Material mat = Material.matchMaterial(materialName);
if (mat == null) {
sender.sendMessage(ChatColor.RED + "Das Material '" + materialName + "' wurde nicht gefunden!");
return true;
}
itemKey = mat.name().toLowerCase();
try {
basePrice = Double.parseDouble(args[2]);
stock = Integer.parseInt(args[3]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Ungültige Preis oder Bestandszahl! Beispiel: /shop add Diamond 20 64");
return true;
}
}
// Fall 2: /shop add <Preis> <Stock> (3 Argumente) -> Nimmt Item in der Hand
else if (args.length == 3) {
ItemStack itemInHand = player.getInventory().getItemInMainHand();
if (itemInHand == null || itemInHand.getType() == Material.AIR) {
sender.sendMessage(ChatColor.RED + "Du musst das Item, das du hinzufügen willst, in der Hand halten!");
sender.sendMessage(ChatColor.GRAY + "Oder benutze: /shop add <Material> <Preis> <Bestand>");
return true;
}
itemKey = itemInHand.getType().name().toLowerCase();
try {
basePrice = Double.parseDouble(args[1]);
stock = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
sender.sendMessage(getMessage("number-error"));
return true;
}
}
// Falsche Anzahl an Argumenten
else {
sender.sendMessage(ChatColor.RED + "Falsche Benutzung!");
sender.sendMessage(ChatColor.GRAY + "Mit Item in Hand: /shop add <Preis> <Bestand>");
sender.sendMessage(ChatColor.GRAY + "Mit Namen: /shop add <Material> <Preis> <Bestand>");
return true;
try {
basePrice = Double.parseDouble(args[1]);
stock = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
sender.sendMessage(getMessage("number-error"));
return false;
}
shopManager.addOrUpdateItem(itemKey, basePrice, stock);
String msg = getMessage("item-added")
.replace("{item}", itemKey)
.replace("{price}", String.valueOf(basePrice))
.replace("{stock}", String.valueOf(stock));
.replace("{price}", args[1])
.replace("{stock}", args[2]);
sender.sendMessage(msg);
return true;

View File

@@ -2,10 +2,7 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.SurvivalPlus;
import de.viper.survivalplus.listeners.SitListener;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -41,50 +38,20 @@ public class SitCommand implements CommandExecutor {
return true;
}
// --- ÄNDERUNG: Wenn man schon sitzt, tue nichts ---
// Prüfe, ob der Spieler bereits sitzt
if (sitListener.isSitting(player)) {
player.sendMessage(ChatColor.RED + "Du sitzt bereits!");
return true;
}
// --------------------------------------------------
if (player.isInsideVehicle()) {
player.sendMessage(lang.getString("sit.already-in-vehicle", "§cDu kannst dich nicht setzen, während du in einem Fahrzeug bist!"));
sitListener.standUp(player);
player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!"));
return true;
}
// Prüfe den Boden unter dem Spieler
Location loc = player.getLocation();
Block blockBelow = loc.clone().subtract(0, 1, 0).getBlock();
if (blockBelow.getType().isAir() || !blockBelow.getType().isSolid()) {
player.sendMessage(plugin.getLangConfig().getString("sit.no-solid-ground", "§cDu kannst dich hier nicht hinsetzen (kein fester Boden)."));
return true;
}
// Position vorbereiten
// Setze den Spieler mittig auf den Block und leicht oberhalb (Fix)
Location location = player.getLocation();
location.setX(location.getBlockX() + 0.5);
location.setY(location.getBlockY() + 0.25);
location.setZ(location.getBlockZ() + 0.5);
// Höhe dynamisch berechnen
double y = location.getBlockY();
if (isStair(blockBelow.getType()) || isSlab(blockBelow.getType())) {
y += 0.5;
}
location.setY(y);
sitListener.sitPlayer(player, location);
return true;
}
private boolean isStair(Material material) {
return material.name().endsWith("_STAIRS");
}
private boolean isSlab(Material material) {
return material.name().endsWith("_SLAB") || material.name().endsWith("_STEP") || material.name().equals("PRISMARINE_SLAB");
}
}

View File

@@ -2,12 +2,9 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.Manager.BlockManager;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.configuration.file.FileConfiguration;
public class UnblockCommand implements CommandExecutor {
@@ -23,40 +20,29 @@ public class UnblockCommand implements CommandExecutor {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(color(config.getString("messages.general.only_players", "§cDieser Befehl ist nur für Spieler!")));
return true;
}
if (!player.hasPermission("survivalplus.unblock")) {
player.sendMessage(color(config.getString("messages.general.no_permission", "§cDu hast keine Berechtigung für diesen Befehl!")));
sender.sendMessage(config.getString("messages.general.only_players"));
return true;
}
if (args.length != 1) {
player.sendMessage(color(config.getString("messages.block.usage", "§cVerwendung: /unblock <Spieler>")));
player.sendMessage(config.getString("messages.unblock.usage"));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null || target == player) {
player.sendMessage(color(config.getString("messages.block.invalid_player", "§cDieser Spieler konnte nicht gefunden werden oder bist du selbst.")));
player.sendMessage(config.getString("messages.unblock.invalid_player"));
return true;
}
if (!blockManager.hasBlocked(player, target)) {
String msg = config.getString("messages.block.not_blocked", "&cDieser Spieler ist nicht blockiert.");
player.sendMessage(color(msg.replace("%player%", target.getName())));
} else {
if (blockManager.hasBlocked(player, target)) {
blockManager.unblockPlayer(player, target);
String msg = config.getString("messages.block.unblocked", "&aDu hast %player% entblockt.");
player.sendMessage(color(msg.replace("%player%", target.getName())));
player.sendMessage(config.getString("messages.unblock.unblocked").replace("%player%", target.getName()));
target.sendMessage(config.getString("messages.unblock.unblocked_by").replace("%player%", player.getName()));
} else {
player.sendMessage(config.getString("messages.unblock.not_blocked").replace("%player%", target.getName()));
}
return true;
}
private String color(String msg) {
if (msg == null) return "";
return ChatColor.translateAlternateColorCodes('&', msg);
}
}

View File

@@ -42,7 +42,8 @@ public class ShopGui implements Listener {
for (String itemKey : shopManager.getShopConfig().getConfigurationSection("items").getKeys(false)) {
Material mat = getMaterialFromKey(itemKey);
if (mat == null) {
continue; // Unbekannte Materialien einfach überspringen
player.sendMessage(ChatColor.RED + "Material für '" + itemKey + "' konnte nicht gefunden werden.");
continue;
}
addShopItem(itemKey, mat, 64);
@@ -63,7 +64,6 @@ public class ShopGui implements Listener {
private void addShopItem(String itemKey, Material material, int amount) {
double pricePerUnit = shopManager.getCurrentPrice(itemKey);
int currentStock = shopManager.getStock(itemKey);
double totalPrice = pricePerUnit * amount;
ItemStack item = new ItemStack(material, amount);
@@ -71,12 +71,9 @@ public class ShopGui implements Listener {
if (meta != null) {
meta.setDisplayName(ChatColor.GREEN.toString() + amount + "x " + material.name());
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Lagerbestand: " + ChatColor.YELLOW + currentStock,
ChatColor.YELLOW + "Preis pro Stück: " + pricePerUnit,
ChatColor.YELLOW + "Gesamtpreis: " + totalPrice,
"",
ChatColor.GREEN + "Linksklick zum Kaufen",
ChatColor.RED + "Rechtsklick zum Verkaufen"
ChatColor.GRAY + "Klicke, um zu kaufen"
));
item.setItemMeta(meta);
}
@@ -104,54 +101,25 @@ public class ShopGui implements Listener {
Material mat = clicked.getType();
String itemKey = mat.name().toLowerCase();
if (e.isLeftClick()) {
// --- KAUFEN ---
if (!shopManager.buyItem(itemKey, amount)) {
player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop.");
return;
}
double totalPrice = shopManager.getCurrentPrice(itemKey) * amount;
// TODO: Economy Abzug hier einfügen!
// Beispiel: if (!economy.withdrawPlayer(player, totalPrice)) { ... }
player.getInventory().addItem(new ItemStack(mat, amount));
player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", totalPrice) + " gekauft.");
} else if (e.isRightClick()) {
// --- VERKAUFEN ---
// Prüfen ob Spieler genug Items hat
if (!player.getInventory().containsAtLeast(new ItemStack(mat), amount)) {
player.sendMessage(ChatColor.RED + "Du hast nicht genug Items zum Verkaufen.");
return;
}
// Verkaufslogik
shopManager.sellItem(itemKey, amount);
double sellPrice = shopManager.getCurrentPrice(itemKey) * amount; // Neuer Preis nach dem Verkauf wird genutzt
// Hinweis: In echten Systemen bekommt man oft den Preis *vor* dem Preisverfall, hier nehmen wir den neuen für Einfachheit oder den gespeicherten:
// Besser wäre: double oldPrice = price * amount; ... player.giveMoney(oldPrice);
// TODO: Economy Gutschrift hier einfügen!
player.getInventory().removeItem(new ItemStack(mat, amount));
player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", sellPrice) + " verkauft.");
if (!shopManager.buyItem(itemKey, amount)) {
player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop.");
return;
}
// Inventory schließen, um Preise neu zu laden und Bugs zu vermeiden
double totalPrice = shopManager.getCurrentPrice(itemKey) * amount;
// TODO: Economy Abzug einfügen
player.getInventory().addItem(new ItemStack(mat, amount));
player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + totalPrice + " Coins gekauft.");
player.closeInventory();
// Optional: direkt neu öffnen mit createInventory() wenn es flüssig sein soll
// createInventory();
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent e) {
if (e.getView().getTitle().equals("Shop") && e.getPlayer().equals(player)) {
// Listener kann entladen werden, wenn nicht mehr gebraucht
// HandlerList.unregisterAll(this); // Vorsicht: falls Singleton, sonst fliegt allen der GUI weg
// Optional: Listener entfernen oder Aufräumarbeiten hier
}
}

View File

@@ -1,122 +0,0 @@
package de.viper.survivalplus.listeners;
import de.viper.survivalplus.SurvivalPlus;
import org.bukkit.ChatColor;
import org.bukkit.attribute.Attribute;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import java.util.UUID;
public class AdaptiveMobListener implements Listener {
private final SurvivalPlus plugin;
// Konfiguration für die Anpassung
private final double SCALING_FACTOR = 0.1; // 10% mehr Stärke pro Level
private final int KILLS_PER_LEVEL = 5; // Alle 5 Kills steigt das Level
private final int MAX_LEVEL = 50; // Maximales Level (Cap)
public AdaptiveMobListener(SurvivalPlus plugin) {
this.plugin = plugin;
}
// 1. Verhalten tracken: Zählt Kills des Spielers
@EventHandler
public void onMobDeath(EntityDeathEvent event) {
if (event.getEntity().getKiller() instanceof Player) {
Player killer = event.getEntity().getKiller();
UUID uuid = killer.getUniqueId();
FileConfiguration mobConfig = plugin.getMobAdaptConfig();
String uuidPath = uuid.toString();
int currentLevel = mobConfig.getInt(uuidPath + ".level", 0);
int currentKills = mobConfig.getInt(uuidPath + ".kills", 0);
currentKills++;
// Level up wenn genug Kills
if (currentKills >= KILLS_PER_LEVEL && currentLevel < MAX_LEVEL) {
currentLevel++;
currentKills = 0;
killer.sendMessage(ChatColor.GREEN + "[SurvivalPlus] " + ChatColor.GRAY + "Die Monster werden aggressiver! (Threat Level: " + currentLevel + ")");
}
// Speichern
mobConfig.set(uuidPath + ".level", currentLevel);
mobConfig.set(uuidPath + ".kills", currentKills);
plugin.saveMobAdaptConfig();
}
}
// 2. Anpassung: Monster werden beim Spawnen angepasst
@EventHandler
public void onCreatureSpawn(CreatureSpawnEvent event) {
// Nur aktivieren, wenn in Config gewünscht
if (!plugin.getConfig().getBoolean("adaptive-mobs.enabled", true)) return;
// Wir greifen nur bei natürlichen Spawns ein (oder Spawn-Eiern), aber nicht bei Spawner-Blöcken (Farmen schützen)
if (event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL &&
event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.SPAWNER_EGG &&
event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.DISPENSE_EGG) {
return;
}
LivingEntity entity = event.getEntity();
// Keine Anpassung für friedliche Tiere
if (entity instanceof Animals || entity instanceof Ambient) return;
// Nächsten Spieler suchen (Radius: 30 Blöcke)
Player nearestPlayer = null;
double minDistance = 30.0;
for (Player p : entity.getWorld().getPlayers()) {
if (p.getLocation().distance(entity.getLocation()) <= minDistance) {
nearestPlayer = p;
minDistance = p.getLocation().distance(entity.getLocation());
}
}
if (nearestPlayer != null) {
applyAttributesBasedOnPlayer(entity, nearestPlayer);
}
}
private void applyAttributesBasedOnPlayer(LivingEntity entity, Player player) {
FileConfiguration mobConfig = plugin.getMobAdaptConfig();
int level = mobConfig.getInt(player.getUniqueId().toString() + ".level", 0);
if (level == 0) return;
// Multiplikator berechnen (z.B. Level 10 -> 1.0 + (10 * 0.1) = 2.0 = doppelte Stärke)
double multiplier = 1.0 + (level * SCALING_FACTOR);
// Cap bei 5-facher Stärke, damit es nicht unspielbar wird
if (multiplier > 5.0) multiplier = 5.0;
// Lebenspunkte erhöhen
if (entity.getAttribute(Attribute.GENERIC_MAX_HEALTH) != null) {
double baseHealth = entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(baseHealth * multiplier);
entity.setHealth(entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue());
}
// Angriffsschaden erhöhen
if (entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE) != null) {
double baseDamage = entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE).getBaseValue();
entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE).setBaseValue(baseDamage * multiplier);
}
// Optional: Visuelles Indiz (Name über dem Kopf), wenn Monster sehr stark sind
if (level >= 10) {
entity.setCustomName(ChatColor.RED + "" + ChatColor.GRAY + "Starkes Monster (Lvl " + level + ")");
entity.setCustomNameVisible(true);
}
}
}

View File

@@ -2,23 +2,13 @@ package de.viper.survivalplus.listeners;
import de.viper.survivalplus.SurvivalPlus;
import de.viper.survivalplus.util.Claim;
import org.bukkit.Location;
import org.bukkit.entity.Monster;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ClaimListener implements Listener {
private final SurvivalPlus plugin;
private final Map<UUID, Claim> lastClaim = new HashMap<>();
private SurvivalPlus plugin;
public ClaimListener(SurvivalPlus plugin) {
this.plugin = plugin;
@@ -26,75 +16,19 @@ public class ClaimListener implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
if (player.isOp()) return;
Claim claim = plugin.getClaim(event.getBlock().getLocation());
if (claim != null && !claim.getOwner().equals(player.getUniqueId()) && !claim.getTrusted().contains(player.getUniqueId())) {
if (claim != null && !claim.canInteract(event.getPlayer().getUniqueId()) && !event.getPlayer().isOp()) {
event.setCancelled(true);
player.sendMessage(plugin.getMessage("claim.no-break"));
event.getPlayer().sendMessage(plugin.getMessage("claim.no-break"));
}
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
Player player = event.getPlayer();
if (player.isOp()) return;
Claim claim = plugin.getClaim(event.getBlock().getLocation());
if (claim != null && !claim.getOwner().equals(player.getUniqueId()) && !claim.getTrusted().contains(player.getUniqueId())) {
if (claim != null && !claim.canInteract(event.getPlayer().getUniqueId()) && !event.getPlayer().isOp()) {
event.setCancelled(true);
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);
}
}
// --- NEU: Schutz vor Monstern im Claim ---
@EventHandler
public void onMobTarget(EntityTargetLivingEntityEvent event) {
// 1. Prüfen, ob das Ziel ein Spieler ist
if (!(event.getTarget() instanceof Player)) return;
// 2. Prüfen, ob das Angreifer ein Monster ist (Zombie, Skelett, etc.)
if (!(event.getEntity() instanceof Monster)) return;
Player player = (Player) event.getTarget();
Location loc = player.getLocation();
// 3. Prüfen, ob der Spieler in einem Claim steht
Claim claim = plugin.getClaim(loc);
if (claim != null) {
// 4. Prüfen, ob der Spieler dort Rechte hat (Owner oder Trusted)
if (claim.getOwner().equals(player.getUniqueId()) || claim.getTrusted().contains(player.getUniqueId())) {
// Das Targeting abbrechen -> Monster greift nicht an
event.setCancelled(true);
}
event.getPlayer().sendMessage(plugin.getMessage("claim.no-place"));
}
}
}

View File

@@ -26,15 +26,10 @@ public class NewbieProtectionListener implements Listener {
private final SurvivalPlus plugin;
private final boolean enabled;
private final int durationMinutes;
private final int COOLDOWN_SECONDS = 60; // Abklingzeit für Nachrichten in Sekunden (1 Minute)
// Maps für Zeiten, BossBars und Nachrichten-Abklingzeit
// Maps für Zeiten und BossBars
private final Map<UUID, Integer> remainingSeconds = new HashMap<>();
private final Map<UUID, BossBar> bossBars = new HashMap<>();
private final Map<UUID, Long> messageCooldowns = new HashMap<>();
// NEU: Set um zu speichern, wer schon mal gejoint ist
private final Set<UUID> hasJoined = new HashSet<>();
// YAML Datei
private File dataFile;
@@ -62,23 +57,17 @@ public class NewbieProtectionListener implements Listener {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
boolean isFirstJoin = !hasJoined.contains(uuid);
// Falls nicht gespeichert, neue Zeit einstellen
remainingSeconds.putIfAbsent(uuid, durationMinutes * 60);
if (isFirstJoin) {
// Erster Join EVER -> Schutz geben und als "gejoint" markieren
remainingSeconds.put(uuid, durationMinutes * 60);
hasJoined.add(uuid);
createBossBar(player, remainingSeconds.get(uuid));
} else {
// Spieler war schonmal da -> Prüfen ob noch Schutz übrig ist
Integer timeLeft = remainingSeconds.get(uuid);
if (timeLeft != null && timeLeft > 0) {
// Schutz läuft noch -> Bossbar wiederherstellen
createBossBar(player, timeLeft);
}
// Wenn timeLeft null ist oder 0, passiert nichts -> Kein Schutz für den "alten Hasen"
}
// Bossbar erstellen/anzeigen
BossBar bar = Bukkit.createBossBar(
ChatColor.GREEN + "Neulingsschutz: " + formatTime(remainingSeconds.get(uuid)),
BarColor.GREEN,
BarStyle.SOLID
);
bar.addPlayer(player);
bossBars.put(uuid, bar);
}
@EventHandler
@@ -86,13 +75,12 @@ public class NewbieProtectionListener implements Listener {
if (!enabled) return;
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
// Bossbar entfernen, aber Status (Zeit & Joined) in Map/Datei behalten
// Bossbar entfernen, aber Zeit bleibt in Map bestehen
BossBar bar = bossBars.remove(uuid);
if (bar != null) {
bar.removeAll();
}
saveData();
saveData(); // Beim Verlassen direkt speichern
}
@EventHandler
@@ -101,27 +89,14 @@ public class NewbieProtectionListener implements Listener {
if (!(event.getEntity() instanceof Player)) return;
Player victim = (Player) event.getEntity();
UUID victimId = victim.getUniqueId();
Integer timeLeft = remainingSeconds.get(victimId);
UUID vid = victim.getUniqueId();
Integer timeLeft = remainingSeconds.get(vid);
// Nur schützen, wenn Zeit > 0
if (timeLeft == null || timeLeft <= 0) return;
event.setCancelled(true);
// Prüfe Cooldown für Nachrichten
long currentTime = System.currentTimeMillis() / 1000;
if (currentTime >= messageCooldowns.getOrDefault(victimId, 0L) + COOLDOWN_SECONDS) {
if (timeLeft != null && timeLeft > 0) {
event.setCancelled(true);
victim.sendMessage(ChatColor.GREEN + "Du bist noch im Neulingsschutz!");
messageCooldowns.put(victimId, currentTime);
}
if (event.getDamager() instanceof Player) {
Player damager = (Player) event.getDamager();
UUID damagerId = damager.getUniqueId();
if (currentTime >= messageCooldowns.getOrDefault(damagerId, 0L) + COOLDOWN_SECONDS) {
damager.sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!");
messageCooldowns.put(damagerId, currentTime);
if (event.getDamager() instanceof Player) {
((Player) event.getDamager()).sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!");
}
}
}
@@ -134,15 +109,16 @@ public class NewbieProtectionListener implements Listener {
Player p = Bukkit.getPlayer(uuid);
if (p == null || !p.isOnline()) {
// Spieler offline -> Zeit pausiert
// Spieler offline Zeit pausiert
continue;
}
int timeLeft = remainingSeconds.getOrDefault(uuid, 0);
if (timeLeft <= 0) {
// Zeit abgelaufen -> Bossbar weg, aber aus hasJoined NICHT löschen
removeProtection(uuid);
// Ablauf: Bossbar weg + Map clean
BossBar bar = bossBars.remove(uuid);
if (bar != null) bar.removeAll();
remainingSeconds.remove(uuid);
continue;
}
@@ -151,41 +127,17 @@ public class NewbieProtectionListener implements Listener {
remainingSeconds.put(uuid, timeLeft);
// Bossbar updaten
updateBossBar(p, timeLeft);
BossBar bar = bossBars.get(uuid);
if (bar != null) {
bar.setTitle(ChatColor.GREEN + "Neulingsschutz: " + formatTime(timeLeft));
bar.setProgress(Math.max(0, timeLeft / (float) (durationMinutes * 60)));
}
}
// Jedes Mal zyklisch speichern
saveData();
saveData(); // zyklisch speichern
}
}.runTaskTimer(plugin, 20L, 20L);
}
private void removeProtection(UUID uuid) {
remainingSeconds.remove(uuid);
BossBar bar = bossBars.remove(uuid);
if (bar != null) {
bar.removeAll();
}
}
private void createBossBar(Player player, int seconds) {
BossBar bar = Bukkit.createBossBar(
ChatColor.GREEN + "Neulingsschutz: " + formatTime(seconds),
BarColor.GREEN,
BarStyle.SOLID
);
bar.addPlayer(player);
bar.setProgress(Math.max(0, seconds / (float) (durationMinutes * 60)));
bossBars.put(player.getUniqueId(), bar);
}
private void updateBossBar(Player player, int seconds) {
BossBar bar = bossBars.get(player.getUniqueId());
if (bar != null) {
bar.setTitle(ChatColor.GREEN + "Neulingsschutz: " + formatTime(seconds));
bar.setProgress(Math.max(0, seconds / (float) (durationMinutes * 60)));
}
}
// ---------- Datei Handling ----------
private void loadData() {
dataFile = new File(plugin.getDataFolder(), "newbieprotection.yml");
@@ -197,47 +149,21 @@ public class NewbieProtectionListener implements Listener {
}
}
dataConfig = YamlConfiguration.loadConfiguration(dataFile);
hasJoined.clear();
remainingSeconds.clear();
for (String key : dataConfig.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
// Gelesen: 'joined' Status und 'seconds'
boolean joined = dataConfig.getBoolean(key + ".joined", false);
int sec = dataConfig.getInt(key + ".seconds", 0);
if (joined) {
hasJoined.add(uuid);
}
if (sec > 0) {
remainingSeconds.put(uuid, sec);
}
int sec = dataConfig.getInt(key);
remainingSeconds.put(uuid, sec);
} catch (IllegalArgumentException ignored) {}
}
}
// WICHTIG: Public gemacht, damit von SurvivalPlus.java aufrufbar
public void saveData() {
dataConfig = YamlConfiguration.loadConfiguration(dataFile);
// Wir speichern ALLE Spieler, die schon mal gejoint sind (hasJoined)
// damit wir beim Rejoin wissen, dass sie keinen Neulingsschutz mehr bekommen.
for (UUID uuid : hasJoined) {
dataConfig.set(uuid + ".joined", true);
Integer sec = remainingSeconds.get(uuid);
if (sec != null && sec > 0) {
dataConfig.set(uuid + ".seconds", sec);
} else {
// Wenn Schutz abgelaufen (0 oder nicht in Map), speichern wir 0 oder löschen wir den Key?
// Besser: löschen wir den Key "seconds", aber "joined" bleibt true.
dataConfig.set(uuid + ".seconds", null);
}
if (dataConfig == null) return;
for (Map.Entry<UUID, Integer> entry : remainingSeconds.entrySet()) {
dataConfig.set(entry.getKey().toString(), entry.getValue());
}
try {
dataConfig.save(dataFile);
} catch (IOException e) {

View File

@@ -1,13 +0,0 @@
package de.viper.survivalplus.listeners;
import de.viper.survivalplus.SurvivalPlus;
/**
* Dieser Listener wird nicht mehr benötigt, da NickLoadListener
* mit ProtocolLib die Join/Quit Messages direkt auf Packet-Ebene behandelt.
*
* Diese Klasse kann gelöscht werden oder bleibt als leere Klasse bestehen.
*/
public class NickJoinMessageListener {
// Nicht mehr benötigt - ProtocolLib übernimmt die Arbeit
}

View File

@@ -2,13 +2,10 @@ package de.viper.survivalplus.listeners;
import de.viper.survivalplus.SurvivalPlus;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.entity.Player;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -22,78 +19,26 @@ public class NickLoadListener implements Listener {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Immer die Standard-Join-Message unterdrücken — wir senden eine einzelne kontrollierte Nachricht später
event.setJoinMessage(null);
String rawNick = plugin.getNicknamesConfig().getString(player.getUniqueId().toString());
if (rawNick != null && !rawNick.isEmpty()) {
String coloredNick = translateColors(rawNick);
String plainNick = ChatColor.stripColor(coloredNick);
// Kurz: DisplayName jetzt auf echten Namen lassen, damit andere Plugins / Bukkit intern konsistent sind.
player.setDisplayName(player.getName());
// In 1 Tick: DisplayName für Chat setzen UND die Join-Nachricht EINMAL manuell versenden
Bukkit.getScheduler().runTask(plugin, () -> {
// Chat-Display: [Nick]
player.setDisplayName("§f[" + coloredNick + "§f]");
// Nachricht an alle Spieler senden (keine Doppelung, weil wir event.setJoinMessage(null) gesetzt haben)
String message = coloredNick + " §ejoined the game";
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(message);
}
// Log in Konsole (sauber, ohne Farb-Codes)
plugin.getLogger().info(plainNick + " joined the game");
});
} else {
// Kein Nick vorhanden -> benutze Standardverhalten, aber ebenfalls manuell, um Doppelungen zu verhindern
player.setDisplayName(player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
String message = player.getName() + " §ejoined the game";
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(message);
}
plugin.getLogger().info(player.getName() + " joined the game");
});
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
// Standard-Quit-Message unterdrücken
event.setQuitMessage(null);
String rawNick = plugin.getNicknamesConfig().getString(player.getUniqueId().toString());
if (rawNick != null && !rawNick.isEmpty()) {
String coloredNick = translateColors(rawNick);
String plainNick = ChatColor.stripColor(coloredNick);
// Sende Quit-Nachricht einmal manuell
String msg = coloredNick + " §eleft the game";
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(msg);
}
plugin.getLogger().info(plainNick + " left the game");
} else {
String msg = player.getName() + " §eleft the game";
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(msg);
}
plugin.getLogger().info(player.getName() + " left the game");
String finalNick = "§f[" + coloredNick + "§f]";
player.setDisplayName(finalNick);
player.setPlayerListName(finalNick);
}
}
private String translateColors(String input) {
String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input);
Matcher matcher = HEX_PATTERN.matcher(withLegacy);
return replaceHexColors(withLegacy);
}
private String replaceHexColors(String input) {
Matcher matcher = HEX_PATTERN.matcher(input);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String hexCode = matcher.group();

View File

@@ -1,23 +1,19 @@
package de.viper.survivalplus.listeners;
import de.viper.survivalplus.SurvivalPlus;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.event.block.Action;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -26,12 +22,9 @@ import java.util.logging.Level;
public class SitListener implements Listener {
private final SurvivalPlus plugin;
private final Map<UUID, ArmorStand> sittingPlayers = new HashMap<>();
private final Map<UUID, Long> sitCooldown = new HashMap<>(); // Neuer Cooldown gegen "Auto-Standup"
private static final String SIT_TAG = "SP_SIT_STAND";
public SitListener(SurvivalPlus plugin) {
this.plugin = plugin;
cleanUpGhostStands();
}
public boolean isSitting(Player player) {
@@ -49,50 +42,22 @@ public class SitListener implements Listener {
armorStand.setMarker(true);
armorStand.setInvisible(true);
armorStand.setInvulnerable(true);
armorStand.addScoreboardTag(SIT_TAG);
// Setze Rotation des Stands auf Rotation des Spielers
armorStand.setRotation(player.getLocation().getYaw(), player.getLocation().getPitch());
// Füge Spieler als Passenger hinzu
armorStand.addPassenger(player);
// FIX: KEIN player.teleport() hier ausführen!
// In neueren Versionen "bricht" das Teleportieren das Mounten sofort wieder.
// Der Spieler wird automatisch durch addPassenger zum Stand teleportiert.
// Cooldown setzen, damit wir nicht sofort durch "Movement" aufstehen
sitCooldown.put(player.getUniqueId(), System.currentTimeMillis() + 500L);
sittingPlayers.put(player.getUniqueId(), armorStand);
FileConfiguration lang = plugin.getLangConfig();
player.sendMessage(lang.getString("sit.success", "§aDu hast dich hingesetzt!"));
plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " sitzt bei " + locationToString(location));
}
public void standUp(Player player) {
UUID playerId = player.getUniqueId();
ArmorStand armorStand = sittingPlayers.remove(playerId);
sitCooldown.remove(playerId); // Cooldown löschen
if (armorStand != null) {
if (player.isValid() && armorStand.isValid()) {
// Wenn man absteigt, landet man oft leicht daneben. Wir korrigieren das leicht,
// aber nicht so aggressiv wie beim Hinsetzen.
Location loc = player.getLocation().add(0, 0.2, 0);
armorStand.removePassenger(player);
armorStand.remove();
// Kleiner Fix, damit man nicht im Boden klebt
player.teleport(loc);
} else {
armorStand.remove();
}
armorStand.remove();
FileConfiguration lang = plugin.getLangConfig();
if (player.isOnline()) {
player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!"));
}
player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!"));
plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " ist aufgestanden");
}
}
@@ -106,6 +71,7 @@ public class SitListener implements Listener {
FileConfiguration lang = plugin.getLangConfig();
if (!player.hasPermission("survivalplus.sit")) {
player.sendMessage(lang.getString("no-permission", "§cDu hast keine Berechtigung für diesen Befehl!"));
return;
}
@@ -114,64 +80,35 @@ public class SitListener implements Listener {
return;
}
double distance = player.getLocation().distance(block.getLocation());
if (distance > 3.0) {
return;
}
// Wenn der Spieler bereits sitzt, stehe auf
if (sittingPlayers.containsKey(player.getUniqueId())) {
standUp(player);
event.setCancelled(true);
return;
}
if (player.isInsideVehicle()) return;
// Position berechnen
// Setze den Spieler genau auf der Treppenstufe
Location location = block.getLocation();
location.setX(location.getX() + 0.5);
location.setY(location.getY() + 0.5); // Genau auf der Treppenstufe (halbe Blockhöhe)
location.setZ(location.getZ() + 0.5);
// Bei Treppen: Y + 0.5 ist meistens korrekt für die Sitzfläche
location.setY(location.getY() + 0.5);
// Drehung anpassen, damit man nicht auf der Treppe schräg sitzt
location.setYaw(player.getLocation().getYaw());
location.setPitch(player.getLocation().getPitch());
sitPlayer(player, location);
event.setCancelled(true);
event.setCancelled(true); // Verhindere andere Interaktionen mit der Treppe
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
// Wenn der Spieler sitzt
if (sittingPlayers.containsKey(playerId)) {
// FIX: Cooldown prüfen (Verhindert "Auto-Standup" durch Lag beim Hinsetzen)
Long cooldown = sitCooldown.get(playerId);
if (cooldown != null && cooldown > System.currentTimeMillis()) {
return;
}
Location from = event.getFrom();
Location to = event.getTo();
if (to == null) return;
// Prüfe signifikante Bewegung
if (from.distanceSquared(to) > 0.0025) {
standUp(player);
}
if (!sittingPlayers.containsKey(playerId)) {
return;
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
if (sittingPlayers.containsKey(event.getPlayer().getUniqueId())) {
standUp(event.getPlayer());
// Prüfe, ob der Spieler sich bewegt hat (nur Positionsänderung, nicht Kopfbewegung)
Location from = event.getFrom();
Location to = event.getTo();
if (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()) {
standUp(player);
}
}
@@ -185,18 +122,7 @@ public class SitListener implements Listener {
return material.name().endsWith("_STAIRS");
}
private void cleanUpGhostStands() {
Bukkit.getScheduler().runTaskLater(plugin, () -> {
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand) {
if (entity.getScoreboardTags().contains(SIT_TAG)) {
entity.remove();
}
}
}
}
plugin.getLogger().info("Ghost Stands (Sit) bereinigt.");
}, 20L);
private String locationToString(Location loc) {
return String.format("x=%.2f, y=%.2f, z=%.2f, world=%s", loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName());
}
}

View File

@@ -56,12 +56,19 @@ public class ToolUpgradeListener implements Listener {
if (!(event.getInventory() instanceof CraftingInventory)) return;
CraftingInventory inv = (CraftingInventory) event.getInventory();
ItemStack[] matrix = inv.getMatrix();
if (matrix.length < 9) return; // Kein 3x3-Crafting-Tisch
if (matrix.length < 9) {
inv.setResult(null);
return;
}
ItemStack center = matrix[4];
// Nur eingreifen, wenn ein Werkzeug in der Mitte liegt
if (center == null || center.getType() == Material.AIR || !isUpgradeableTool(center.getType()) || isWoodTool(center.getType())) {
return; // Standard-Rezepte nicht beeinflussen
if (center == null || center.getType() == Material.AIR) {
inv.setResult(null);
return;
}
if (!isUpgradeableTool(center.getType()) || isWoodTool(center.getType())) {
inv.setResult(null);
return;
}
int currentLevel = getToolLevel(center);
@@ -76,7 +83,7 @@ public class ToolUpgradeListener implements Listener {
return;
}
// Prüfen, ob alle Slots (außer Mitte) mind. 2 Stück vom richtigen Material haben
// Prüfen ob alle Slots (außer Mitte) mind. 2 Stück vom richtigen Material haben
for (int i = 0; i < matrix.length; i++) {
if (i == 4) continue;
if (matrix[i] == null || matrix[i].getType() != requiredMat || matrix[i].getAmount() < REQUIRED_AMOUNT) {
@@ -99,33 +106,20 @@ public class ToolUpgradeListener implements Listener {
ItemStack[] matrix = inv.getMatrix();
ItemStack center = matrix[4];
if (center == null || !isUpgradeableTool(center.getType()) || isWoodTool(center.getType())) {
plugin.getLogger().fine("Kein Upgrade-Werkzeug in der Mitte: " + (center != null ? center.getType() : "null"));
return;
}
if (center == null || !isUpgradeableTool(center.getType()) || isWoodTool(center.getType())) return;
int currentLevel = getToolLevel(center);
if (currentLevel >= maxLevel || currentLevel >= getMaxAllowedLevel(center.getType())) {
plugin.getLogger().fine("Maximales Level erreicht für Werkzeug: " + center.getType());
return;
}
if (currentLevel >= maxLevel || currentLevel >= getMaxAllowedLevel(center.getType())) return;
Material requiredMat = levelMaterials.get(currentLevel + 1);
if (requiredMat == null) {
plugin.getLogger().fine("Kein Material für Level " + (currentLevel + 1));
return;
}
if (requiredMat == null) return;
for (int i = 0; i < matrix.length; i++) {
if (i == 4) continue;
if (matrix[i] == null || matrix[i].getType() != requiredMat || matrix[i].getAmount() < REQUIRED_AMOUNT) {
plugin.getLogger().fine("Ungültiges Material in Slot " + i + ": " + (matrix[i] != null ? matrix[i].getType() : "null"));
return;
}
if (matrix[i] == null || matrix[i].getType() != requiredMat || matrix[i].getAmount() < REQUIRED_AMOUNT) return;
}
// Gültig: Event abbrechen, Upgrade geben, Items verbrauchen
plugin.getLogger().info("Werkzeug-Upgrade für " + player.getName() + ": " + center.getType() + " auf Level " + (currentLevel + 1));
event.setCancelled(true);
ItemStack result = createUpgradedTool(center, currentLevel + 1);
@@ -221,9 +215,7 @@ public class ToolUpgradeListener implements Listener {
n.endsWith("_SWORD");
}
private boolean isWoodTool(Material mat) {
return mat.name().startsWith("WOODEN");
}
private boolean isWoodTool(Material mat) { return mat.name().startsWith("WOODEN"); }
private int getMaxAllowedLevel(Material mat) {
String s = mat.name();

View File

@@ -6,7 +6,6 @@ import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
@@ -17,34 +16,23 @@ public class BackpackRecipe {
ItemStack backpack = new ItemStack(Material.CHEST);
ItemMeta meta = backpack.getItemMeta();
if (meta != null) {
// Fallback falls der Key in der lang.yml fehlt
String displayName = langConfig.getString("backpack.name", "&eRucksack");
meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', displayName));
meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', langConfig.getString("backpack.name", "&eRucksack")));
backpack.setItemMeta(meta);
}
NamespacedKey key = new NamespacedKey(plugin, "backpack");
// Rezept erstellen
ShapedRecipe recipe = new ShapedRecipe(key, backpack);
// Form festlegen
recipe.shape(
"S L",
" C ",
"S L"
"S L", // Faden, leer, Leder
" C ", // leer, Truhe, leer
"S L" // Faden, leer, Leder
);
// Zutaten festlegen (mit RecipeChoice für bessere Kompatibilität)
recipe.setIngredient('S', new RecipeChoice.MaterialChoice(Material.STRING));
recipe.setIngredient('L', new RecipeChoice.MaterialChoice(Material.LEATHER));
recipe.setIngredient('C', new RecipeChoice.MaterialChoice(Material.CHEST));
// WICHTIG: Setzt eine eigene Gruppe.
// Verhindert Konflikte mit Vanilla-Rezepten im Rezeptbuch.
recipe.setGroup("survivalplus");
recipe.setIngredient('S', Material.STRING); // Faden
recipe.setIngredient('L', Material.LEATHER); // Leder
recipe.setIngredient('C', Material.CHEST); // Truhe
Bukkit.addRecipe(recipe);
plugin.getLogger().info("Backpack Rezept wurde erfolgreich registriert (Gruppe: survivalplus).");
plugin.getLogger().info("Backpack Rezept wurde registriert.");
}
}

View File

@@ -73,14 +73,13 @@ public class TradeManager {
return;
}
// Neue Session starten
TradeSession session = new TradeSession(plugin, sender, receiver, this);
TradeSession session = new TradeSession(plugin, sender, receiver);
Bukkit.getPluginManager().registerEvents(session, plugin);
activeTrades.put(sender.getUniqueId(), session);
activeTrades.put(receiver.getUniqueId(), session);
session.openInventory();
session.openInventories();
sender.sendMessage(plugin.getLangConfig().getString("trade.started-sender", "§aTrade gestartet mit %player%").replace("%player%", receiver.getName()));
receiver.sendMessage(plugin.getLangConfig().getString("trade.started-receiver", "§a%player% hat dich zu einem Trade eingeladen.").replace("%player%", sender.getName()));
@@ -92,7 +91,7 @@ public class TradeManager {
Player r = session.getReceiver();
if (s != null) activeTrades.remove(s.getUniqueId());
if (r != null) activeTrades.remove(r.getUniqueId());
session.closeInventory();
session.endSession();
}
public TradeSession getTrade(Player player) {

View File

@@ -16,297 +16,150 @@ import org.bukkit.inventory.meta.ItemMeta;
public class TradeSession implements Listener {
private final SurvivalPlus plugin;
private final TradeManager tradeManager;
private final Player sender;
private final Player receiver;
// Wir benutzen nur EIN Inventar für beide (Size 54 = Double Chest)
// Slots 0-17: Angebot von Sender (Editierbar für Sender)
// Slots 18-35: Angebot von Receiver (Editierbar für Receiver)
// Slots 36-44: Trennung / Status
// Slot 45: Abbrechen
// Slot 53: Bestätigen
private final Inventory tradeInv;
private final Inventory invSender;
private final Inventory invReceiver;
private boolean senderConfirmed = false;
private boolean receiverConfirmed = false;
private boolean active = true;
private boolean ended = false; // Flag gegen Rekursion
public TradeSession(SurvivalPlus plugin, Player sender, Player receiver, TradeManager tradeManager) {
public TradeSession(SurvivalPlus plugin, Player sender, Player receiver) {
this.plugin = plugin;
this.tradeManager = tradeManager;
this.sender = sender;
this.receiver = receiver;
String title = plugin.getLangConfig().getString("trade.inventory-title", "Handel")
.replace("%player%", receiver.getName())
.replace("%other%", sender.getName());
String titleForSender = plugin.getLangConfig().getString("trade.inventory-title", "Handel mit %player%")
.replace("%player%", receiver.getName());
String titleForReceiver = plugin.getLangConfig().getString("trade.inventory-title", "Handel mit %player%")
.replace("%player%", sender.getName());
// Wir nutzen null als Owner, damit es ein gemeinsames Inventar ist
this.tradeInv = Bukkit.createInventory(null, 54, title);
setupLayout();
this.invSender = Bukkit.createInventory(sender, 27, titleForSender);
this.invReceiver = Bukkit.createInventory(receiver, 27, titleForReceiver);
addConfirmButton(invSender);
addConfirmButton(invReceiver);
}
private void setupLayout() {
// Trennung zwischen den beiden Angeboten (Glas)
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
ItemMeta glassMeta = glass.getItemMeta();
glassMeta.setDisplayName(" ");
glass.setItemMeta(glassMeta);
for(int i = 36; i < 45; i++) {
tradeInv.setItem(i, glass);
}
// Buttons initialisieren
updateStatusItem();
updateButtons();
}
private void updateButtons() {
// Abbrechen Button (Rot)
ItemStack cancel = new ItemStack(Material.RED_CONCRETE);
ItemMeta cancelMeta = cancel.getItemMeta();
cancelMeta.setDisplayName(plugin.getLangConfig().getString("trade.cancel-button", "§cAbbrechen"));
cancel.setItemMeta(cancelMeta);
tradeInv.setItem(45, cancel);
// Bestätigen Button
Material confirmMat = Material.LIME_CONCRETE;
String confirmName = "§aBestätigen";
// Wenn beide bestätigt haben, ändert sich der Button
if (senderConfirmed && receiverConfirmed) {
confirmMat = Material.GOLD_BLOCK; // Signalisiert Tauschvorgang
confirmName = "§6Handel läuft...";
} else if (senderConfirmed) {
confirmMat = Material.YELLOW_CONCRETE;
confirmName = "§eWarte auf Partner...";
} else if (receiverConfirmed) {
confirmMat = Material.YELLOW_CONCRETE;
confirmName = "§eWarte auf Partner...";
}
ItemStack confirm = new ItemStack(confirmMat);
ItemMeta confirmMeta = confirm.getItemMeta();
confirmMeta.setDisplayName(confirmName);
confirm.setItemMeta(confirmMeta);
tradeInv.setItem(53, confirm);
}
private void updateStatusItem() {
// Slot 40 zeigt den Status an
String statusText = "§eWarte auf Bestätigung...";
if (senderConfirmed && !receiverConfirmed) {
statusText = "§a" + sender.getName() + " §7hat bestätigt.";
} else if (!senderConfirmed && receiverConfirmed) {
statusText = "§a" + receiver.getName() + " §7hat bestätigt.";
} else if (senderConfirmed && receiverConfirmed) {
statusText = "§6Handel wird ausgeführt...";
}
ItemStack status = new ItemStack(Material.BOOK);
ItemMeta statusMeta = status.getItemMeta();
statusMeta.setDisplayName(statusText);
status.setItemMeta(statusMeta);
tradeInv.setItem(40, status);
private void addConfirmButton(Inventory inv) {
ItemStack confirm = new ItemStack(Material.LIME_CONCRETE);
ItemMeta meta = confirm.getItemMeta();
meta.setDisplayName(plugin.getLangConfig().getString("trade.confirm-button", "§aBestätigen"));
confirm.setItemMeta(meta);
inv.setItem(26, confirm);
}
public Player getSender() { return sender; }
public Player getReceiver() { return receiver; }
public void openInventory() {
if (sender.isOnline()) sender.openInventory(tradeInv);
if (receiver.isOnline()) receiver.openInventory(tradeInv);
public void openInventories() {
if (sender.isOnline()) sender.openInventory(invSender);
if (receiver.isOnline()) receiver.openInventory(invReceiver);
}
public void closeInventory() {
active = false;
if (sender.isOnline() && sender.getOpenInventory().getTopInventory() == tradeInv) {
private void returnItems(Player player, Inventory inventory) {
for (int i = 0; i < 26; i++) {
ItemStack item = inventory.getItem(i);
if (item != null) player.getInventory().addItem(item);
}
}
public void endSession() {
if (ended) return; // Rekursion verhindern
ended = true;
// Items zurückgeben
if (sender.isOnline()) returnItems(sender, invSender);
if (receiver.isOnline()) returnItems(receiver, invReceiver);
// Inventories schließen
if (sender.isOnline() && sender.getOpenInventory().getTopInventory() == invSender) {
sender.closeInventory();
}
if (receiver.isOnline() && receiver.getOpenInventory().getTopInventory() == tradeInv) {
if (receiver.isOnline() && receiver.getOpenInventory().getTopInventory() == invReceiver) {
receiver.closeInventory();
}
HandlerList.unregisterAll(this);
}
private void returnItems() {
// Gibt alle Items zurück, falls der Handel abgebrochen wurde
for (int i = 0; i < 36; i++) {
ItemStack item = tradeInv.getItem(i);
if (item != null) {
// Bestimmen, wem das Item gehört
if (i < 18) {
if (sender.isOnline()) sender.getInventory().addItem(item);
} else {
if (receiver.isOnline()) receiver.getInventory().addItem(item);
}
tradeInv.setItem(i, null); // Slot leeren
}
private Inventory getOwnInventory(Player p) {
return p.getUniqueId().equals(sender.getUniqueId()) ? invSender : invReceiver;
}
private Inventory getOtherInventory(Player p) {
return p.getUniqueId().equals(sender.getUniqueId()) ? invReceiver : invSender;
}
private void updateOtherView(Player p) {
Inventory own = getOwnInventory(p);
Inventory other = getOtherInventory(p);
for (int i = 0; i < 26; i++) {
other.setItem(i, own.getItem(i));
}
}
private void executeTrade() {
// Tauschvorgang
// 1. Sender Items zu Receiver
for (int i = 0; i < 18; i++) {
ItemStack item = tradeInv.getItem(i);
if (item != null) {
if (receiver.isOnline()) receiver.getInventory().addItem(item);
tradeInv.setItem(i, null);
}
}
for (int i = 0; i < 26; i++) {
ItemStack itemFromSender = invSender.getItem(i);
ItemStack itemFromReceiver = invReceiver.getItem(i);
// 2. Receiver Items zu Sender
for (int i = 18; i < 36; i++) {
ItemStack item = tradeInv.getItem(i);
if (item != null) {
if (sender.isOnline()) sender.getInventory().addItem(item);
tradeInv.setItem(i, null);
}
if (itemFromSender != null) sender.getInventory().addItem(itemFromSender);
if (itemFromReceiver != null) receiver.getInventory().addItem(itemFromReceiver);
}
String success = plugin.getLangConfig().getString("trade.success", "§aHandel erfolgreich abgeschlossen!");
if (sender.isOnline()) sender.sendMessage(success);
if (receiver.isOnline()) receiver.sendMessage(success);
closeInventory();
endSession();
}
private void resetConfirmations() {
senderConfirmed = false;
receiverConfirmed = false;
}
@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
if (!active) return;
if (!(e.getWhoClicked() instanceof Player p)) return;
if (e.getClickedInventory() == null) return;
if (!p.getUniqueId().equals(sender.getUniqueId()) && !p.getUniqueId().equals(receiver.getUniqueId())) return;
// Nur Klicks im Trade-Inventar behandeln
if (e.getClickedInventory().getSize() != 54 || !e.getClickedInventory().equals(tradeInv)) return;
// Auch Klicks im Bottom Inventory erlauben, aber nicht logisch behandeln (Bukkit regelt das)
if (p.getUniqueId().equals(sender.getUniqueId())) {
handleSenderClick(e);
} else if (p.getUniqueId().equals(receiver.getUniqueId())) {
handleReceiverClick(e);
}
}
private void handleSenderClick(InventoryClickEvent e) {
Inventory top = e.getView().getTopInventory();
Inventory clicked = e.getClickedInventory();
int slot = e.getSlot();
// Abbrechen
if (slot == 45) {
if (clicked == null) return;
// Bestätigen-Button
if (slot == 26 && clicked.equals(top)) {
e.setCancelled(true);
sender.sendMessage(plugin.getLangConfig().getString("trade.cancelled", "§cHandel abgebrochen."));
if (receiver.isOnline()) receiver.sendMessage(plugin.getLangConfig().getString("trade.cancelled-partner", "§cHandel wurde abgebrochen."));
tradeManager.endTrade(this);
if (p.getUniqueId().equals(sender.getUniqueId())) senderConfirmed = true;
if (p.getUniqueId().equals(receiver.getUniqueId())) receiverConfirmed = true;
p.sendMessage(plugin.getLangConfig().getString("trade.confirmed", "§aDu hast den Handel bestätigt!"));
if (senderConfirmed && receiverConfirmed) executeTrade();
return;
}
// Bestätigen
if (slot == 53) {
e.setCancelled(true);
if (receiverConfirmed) {
executeTrade();
} else {
senderConfirmed = !senderConfirmed; // Toggle
sender.sendMessage(senderConfirmed ?
plugin.getLangConfig().getString("trade.confirmed", "§aBestätigt!") :
"§cBestätigung zurückgezogen.");
updateButtons();
updateStatusItem();
}
return;
// Nur das eigene Trade-Inventar editierbar
if (clicked.equals(top)) {
Bukkit.getScheduler().runTaskLater(plugin, () -> {
updateOtherView(p);
resetConfirmations();
}, 1L);
}
// Item-Bewegungen nur im eigenen Bereich (0-17) erlauben
if (slot >= 0 && slot <= 17) {
// Erlaubt
// Wenn ein Item bewegt wird, Bestätigungen zurücksetzen
if (e.getCurrentItem() != null || e.getCursor() != null) {
if (senderConfirmed || receiverConfirmed) {
senderConfirmed = false;
receiverConfirmed = false;
updateButtons();
updateStatusItem();
}
}
return;
}
// Alle anderen Slots sperren (Partner-Seite, Glas, Status)
e.setCancelled(true);
}
private void handleReceiverClick(InventoryClickEvent e) {
int slot = e.getSlot();
// Abbrechen
if (slot == 45) {
e.setCancelled(true);
receiver.sendMessage(plugin.getLangConfig().getString("trade.cancelled", "§cHandel abgebrochen."));
if (sender.isOnline()) sender.sendMessage(plugin.getLangConfig().getString("trade.cancelled-partner", "§cHandel wurde abgebrochen."));
tradeManager.endTrade(this);
return;
}
// Bestätigen
if (slot == 53) {
e.setCancelled(true);
if (senderConfirmed) {
executeTrade();
} else {
receiverConfirmed = !receiverConfirmed; // Toggle
receiver.sendMessage(receiverConfirmed ?
plugin.getLangConfig().getString("trade.confirmed", "§aBestätigt!") :
"§cBestätigung zurückgezogen.");
updateButtons();
updateStatusItem();
}
return;
}
// Item-Bewegungen nur im eigenen Bereich (18-35) erlauben
if (slot >= 18 && slot <= 35) {
// Erlaubt
if (e.getCurrentItem() != null || e.getCursor() != null) {
if (senderConfirmed || receiverConfirmed) {
senderConfirmed = false;
receiverConfirmed = false;
updateButtons();
updateStatusItem();
}
}
return;
}
// Alle anderen Slots sperren
e.setCancelled(true);
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent e) {
if (!active) return;
if (!(e.getPlayer() instanceof Player p)) return;
// Wenn ein Spieler das Inventar schließt, wird der Handel abgebrochen
// Wir prüfen, ob noch Items drin sind, um Duplikate zu vermeiden
if (e.getInventory().equals(tradeInv)) {
if (p.getUniqueId().equals(sender.getUniqueId()) || p.getUniqueId().equals(receiver.getUniqueId())) {
// Verzögern, falls der Server den CloseEvent für beide fast gleichzeitig feuert
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (active) { // Nur wenn nicht schon durch executeTrade geschlossen
returnItems();
if (p.getUniqueId().equals(sender.getUniqueId())) {
if (receiver.isOnline()) receiver.sendMessage("§cDer Partner hat den Handel abgebrochen.");
} else {
if (sender.isOnline()) sender.sendMessage("§cDer Partner hat den Handel abgebrochen.");
}
tradeManager.endTrade(this);
}
}, 1L);
}
if (p.getUniqueId().equals(sender.getUniqueId()) || p.getUniqueId().equals(receiver.getUniqueId())) {
// Kleine Verzögerung, um StackOverflow zu vermeiden
Bukkit.getScheduler().runTaskLater(plugin, this::endSession, 1L);
}
}
}

View File

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

View File

@@ -2,10 +2,7 @@ package de.viper.survivalplus.util;
import de.viper.survivalplus.SurvivalPlus;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.Chest;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -13,11 +10,9 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockRedstoneEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.scheduler.BukkitRunnable;
@@ -32,7 +27,6 @@ public class LockSystem implements Listener, CommandExecutor {
private final Map<Location, LockData> lockedBlocks = new HashMap<>();
private final Set<UUID> lockMode = new HashSet<>();
private final Map<UUID, BukkitRunnable> lockTimeoutTasks = new HashMap<>();
private final Map<UUID, Long> lastDenyMessage = new HashMap<>();
private final File lockFile;
private FileConfiguration lockConfig;
@@ -145,16 +139,7 @@ public class LockSystem implements Listener, CommandExecutor {
if (lockedBlocks.containsKey(loc)) {
player.sendMessage(plugin.getMessage("lock.already-locked"));
} else {
// Lock den Block
lockedBlocks.put(loc, new LockData(uuid.toString()));
// Prüfe auf Doppeltruhe und locke beide Hälften
Block otherChest = getOtherChestHalf(block);
if (otherChest != null) {
Location otherLoc = otherChest.getLocation();
lockedBlocks.put(otherLoc, new LockData(uuid.toString()));
}
saveLocks();
player.sendMessage(plugin.getMessage("lock.locked"));
}
@@ -164,19 +149,7 @@ public class LockSystem implements Listener, CommandExecutor {
}
if (!isLockable) return;
// Prüfe sowohl den geklickten Block als auch die andere Hälfte bei Doppeltruhen
if (!lockedBlocks.containsKey(loc)) {
Block otherChest = getOtherChestHalf(block);
if (otherChest != null) {
loc = otherChest.getLocation();
if (!lockedBlocks.containsKey(loc)) {
return;
}
} else {
return;
}
}
if (!lockedBlocks.containsKey(loc)) return;
LockData lock = lockedBlocks.get(loc);
String playerUUID = uuid.toString();
@@ -189,176 +162,6 @@ public class LockSystem implements Listener, CommandExecutor {
event.setCancelled(true);
}
// NEU: Verhindert das Öffnen von gelockten Türen durch Redstone (Druckplatten, Knöpfe, etc.)
@EventHandler(priority = EventPriority.HIGHEST)
public void onRedstoneChange(BlockRedstoneEvent event) {
Block block = event.getBlock();
// Prüfe, ob der Block eine Tür ist
if (!block.getType().name().contains("DOOR")) return;
Location loc = block.getLocation();
LockData lock = null;
// Prüfe, ob die Tür gelockt ist
if (lockedBlocks.containsKey(loc)) {
lock = lockedBlocks.get(loc);
} else {
// Prüfe auch die andere Hälfte der Tür (oben/unten)
Location above = loc.clone().add(0, 1, 0);
Location below = loc.clone().add(0, -1, 0);
if (lockedBlocks.containsKey(above)) {
lock = lockedBlocks.get(above);
} else if (lockedBlocks.containsKey(below)) {
lock = lockedBlocks.get(below);
}
}
// Wenn keine Lock gefunden wurde, erlaube die Änderung
if (lock == null) return;
// Wenn die Tür gelockt ist und Redstone-Signal empfängt, blockiere die Änderung
// Es sei denn, das Signal kommt von einem berechtigten Spieler (wird über Druckplatten-Event geprüft)
if (event.getNewCurrent() > 0) {
event.setNewCurrent(0);
}
}
// ZUSÄTZLICH: Verhindert Interaktion mit Druckplatten/Knöpfen in der Nähe von gelockten Türen
@EventHandler(priority = EventPriority.HIGHEST)
public void onPressurePlateInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.PHYSICAL) return;
Block block = event.getClickedBlock();
if (block == null) return;
// Prüfe, ob es eine Druckplatte ist
String typeName = block.getType().name();
if (!typeName.contains("PRESSURE_PLATE")) return;
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
// Prüfe alle angrenzenden Blöcke auf gelockte Türen
for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) {
Block relative = block.getRelative(face);
// Prüfe bis zu 2 Blöcke in jede Richtung
for (int i = 0; i < 3; i++) {
if (relative.getType().name().contains("DOOR")) {
Location doorLoc = relative.getLocation();
// Prüfe beide Türhälften
LockData lock = getLockData(doorLoc);
if (lock != null && !hasAccess(player, lock)) {
event.setCancelled(true);
// Anti-Spam: Nur alle 3 Sekunden eine Nachricht senden
long currentTime = System.currentTimeMillis();
Long lastMessage = lastDenyMessage.get(playerUUID);
if (lastMessage == null || (currentTime - lastMessage) > 3000) {
player.sendMessage(plugin.getMessage("lock.block-denied"));
lastDenyMessage.put(playerUUID, currentTime);
}
return;
}
}
relative = relative.getRelative(face);
}
}
}
private boolean isLockedDoor(Location loc) {
return lockedBlocks.containsKey(loc);
}
private LockData getLockData(Location loc) {
LockData lock = lockedBlocks.get(loc);
if (lock == null) {
lock = lockedBlocks.get(loc.clone().add(0, 1, 0));
}
if (lock == null) {
lock = lockedBlocks.get(loc.clone().add(0, -1, 0));
}
return lock;
}
private boolean hasAccess(Player player, LockData lock) {
String playerUUID = player.getUniqueId().toString();
return lock.getOwnerUUID().equals(playerUUID) ||
lock.isFriend(playerUUID) ||
player.isOp();
}
// Hilfsmethode: Findet die andere Hälfte einer Doppeltruhe
private Block getOtherChestHalf(Block block) {
if (block.getType() != Material.CHEST && block.getType() != Material.TRAPPED_CHEST) {
return null;
}
try {
// Versuche moderne BlockData API (1.13+)
if (block.getBlockData() instanceof Chest) {
Chest chestData = (Chest) block.getBlockData();
// Prüfe ob es eine Doppeltruhe ist
if (chestData.getType() == Chest.Type.SINGLE) {
return null;
}
// Finde die Richtung zur anderen Hälfte
BlockFace facing = chestData.getFacing();
BlockFace direction;
if (chestData.getType() == Chest.Type.LEFT) {
// Linke Hälfte: Andere Hälfte ist rechts
direction = getRight(facing);
} else {
// Rechte Hälfte: Andere Hälfte ist links
direction = getLeft(facing);
}
Block otherBlock = block.getRelative(direction);
if (otherBlock.getType() == block.getType()) {
return otherBlock;
}
}
} catch (Exception e) {
// Fallback für ältere Versionen: Prüfe alle angrenzenden Blöcke
for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) {
Block relative = block.getRelative(face);
if (relative.getType() == block.getType()) {
return relative;
}
}
}
return null;
}
private BlockFace getRight(BlockFace face) {
switch (face) {
case NORTH: return BlockFace.EAST;
case EAST: return BlockFace.SOUTH;
case SOUTH: return BlockFace.WEST;
case WEST: return BlockFace.NORTH;
default: return face;
}
}
private BlockFace getLeft(BlockFace face) {
switch (face) {
case NORTH: return BlockFace.WEST;
case WEST: return BlockFace.SOUTH;
case SOUTH: return BlockFace.EAST;
case EAST: return BlockFace.NORTH;
default: return face;
}
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
@@ -371,13 +174,6 @@ public class LockSystem implements Listener, CommandExecutor {
String playerUUID = player.getUniqueId().toString();
if (lock.getOwnerUUID().equals(playerUUID) || lock.isFriend(playerUUID) || player.isOp()) {
// Erlaube das Brechen und entferne beide Locks bei Doppeltruhen
lockedBlocks.remove(loc);
Block otherChest = getOtherChestHalf(block);
if (otherChest != null) {
lockedBlocks.remove(otherChest.getLocation());
}
saveLocks();
return;
}
@@ -437,13 +233,6 @@ public class LockSystem implements Listener, CommandExecutor {
player.sendMessage(plugin.getMessage("lock.no-permission-unlock"));
} else {
lockedBlocks.remove(loc);
// Entferne auch Lock von der anderen Hälfte bei Doppeltruhen
Block otherChest = getOtherChestHalf(targetBlock);
if (otherChest != null) {
lockedBlocks.remove(otherChest.getLocation());
}
saveLocks();
player.sendMessage(plugin.getMessage("lock.unlocked"));
}
@@ -465,16 +254,6 @@ public class LockSystem implements Listener, CommandExecutor {
player.sendMessage(plugin.getMessage("lock.friendadd.not-found"));
} else {
lock.addFriend(friend.getUniqueId().toString());
// Füge Friend auch zur anderen Hälfte bei Doppeltruhen hinzu
Block otherChest = getOtherChestHalf(targetBlock);
if (otherChest != null) {
LockData otherLock = lockedBlocks.get(otherChest.getLocation());
if (otherLock != null) {
otherLock.addFriend(friend.getUniqueId().toString());
}
}
saveLocks();
player.sendMessage(plugin.getMessage("lock.friendadd.success").replace("{player}", friend.getName()));
}
@@ -497,16 +276,6 @@ public class LockSystem implements Listener, CommandExecutor {
player.sendMessage(plugin.getMessage("lock.friendremove.not-found"));
} else {
lock.removeFriend(friend.getUniqueId().toString());
// Entferne Friend auch von der anderen Hälfte bei Doppeltruhen
Block otherChest = getOtherChestHalf(targetBlock);
if (otherChest != null) {
LockData otherLock = lockedBlocks.get(otherChest.getLocation());
if (otherLock != null) {
otherLock.removeFriend(friend.getUniqueId().toString());
}
}
saveLocks();
player.sendMessage(plugin.getMessage("lock.friendremove.success").replace("{player}", friend.getName()));
}

View File

@@ -1,9 +1,3 @@
# Version (Nicht Ändern!)
version: 1.1.0
# Debug-Option
debug-logging: false
# Neulings Schutz
newbie-protection:
enabled: true

View File

@@ -160,7 +160,7 @@ friend:
notify: "&c%s hat deine Freundschaftsanfrage abgelehnt."
list:
header: "&6== Deine Freundesliste =="
header: "&6=== Deine Freundesliste ==="
entry: "&e%s: %s"
entry-offline: "&e%s: %s &7(Zuletzt online: %s)"
online: "&aOnline"
@@ -168,7 +168,7 @@ friend:
unknown: "&7Unbekannt"
date-format: "dd.MM.yyyy HH:mm:ss"
remove-button: "&c[X]"
footer: "&6======================="
footer: "&6====================="
del:
success: "&a%s wurde aus deiner Freundesliste entfernt."
@@ -403,32 +403,24 @@ inventory:
player-only: "§cDieser Befehl ist nur für Spieler!"
claim:
usage: "&cVerwendung: /claim mark <1|2> oder /claim"
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!"
too-large: "&cDer Bereich ist zu groß! Maximal erlaubt: %max% Blöcke²."
overlap: "&cDieser Bereich überschneidet sich mit einem bestehenden Claim!"
max-reached: "&cDu hast das Maximum von %max% Claims erreicht!"
success: "&aBereich beansprucht! (%count%/%max%)"
unclaimed: "&aBereich freigegeben!"
not-owner: "&cDies ist nicht dein Claim!"
point1-set: "&aPunkt 1 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!"
untrusted: "&a%player% ist in diesem Claim nicht mehr vertraut!"
no-break: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke abbauen!"
no-place: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke platzieren!"
op-unclaimed: "&a%count% Claims von %player% wurden entfernt!"
no-claims-found: "&cKeine Claims für %player% gefunden!"
no-claim-at-location: "&cKein Claim an dieser Position!"
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."
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!"
too-large: "&cDer Bereich ist zu groß! Maximal erlaubt: %max% Blöcke²."
overlap: "&cDieser Bereich überschneidet sich mit einem bestehenden Claim!"
max-reached: "&cDu hast das Maximum von %max% Claims erreicht!"
success: "&aBereich beansprucht! (%count%/%max%)"
unclaimed: "&aBereich freigegeben!"
not-owner: "&cDies ist nicht dein Claim!"
point1-set: "&aPunkt 1 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!"
untrusted: "&a%player% ist in diesem Claim nicht mehr vertraut!"
no-break: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke abbauen!"
no-place: "&cDu kannst in diesem beanspruchten Bereich keine Blöcke platzieren!"
op-unclaimed: "&a%count% Claims von %player% wurden entfernt!"
no-claims-found: "&cKeine Claims für %player% gefunden!"
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%"
force-survival:
join-message: "§aDu wurdest in den Survivalmodus gesetzt!"

View File

@@ -1,9 +1,9 @@
name: SurvivalPlus
version: 1.1.1
version: 1.0.8
main: de.viper.survivalplus.SurvivalPlus
api-version: 1.21
softdepend: [LuckPerms, PlaceholderAPI, ProtocolLib]
softdepend: [LuckPerms, PlaceholderAPI]
author: Viper
description: A plugin for enhancing survival gameplay in Minecraft.
@@ -92,7 +92,8 @@ commands:
friend:
description: Verwaltet die Freundesliste
usage: /<command> [add|accept|deny|list|del|tp] [Spielername]
# FIX: permission und permission-message entfernt um Warnung zu verhindern
permission: survivalplus.friend
permission-message: "§cDu hast keine Berechtigung für diesen Befehl."
stats:
description: Zeigt deine Statistiken an
usage: /<command>
@@ -183,11 +184,6 @@ commands:
usage: /<command> <Name>
permission: survivalplus.nick
permission-message: "§cDu hast keine Berechtigung, deinen Nick zu ändern!"
ride:
description: Reite einen Spieler
usage: /<command> [spieler]
permission: survivalplus.ride
permission-message: "§cDu hast keine Berechtigung, Spieler zu reiten!"
lootchests:
description: Zeigt eine Liste aller aktiven Loot-Kisten an. Admins können per Klick zu einer Kiste teleportieren.
usage: /<command>
@@ -273,6 +269,12 @@ commands:
description: Manages claims for anti-griefing
usage: /<command> [unclaim | trust <player> | untrust <player>]
aliases: [cl]
survivalplus.claim.use:
description: Allows claiming and unclaiming chunks
default: true
survivalplus.claim.trust:
description: Allows trusting/untrusting players in claims
default: true
permissions:
survivalplus.*:
@@ -324,8 +326,6 @@ permissions:
survivalplus.blocklist: true
survivalplus.kit: true
survivalplus.nick: true
survivalplus.ride: true
survivalplus.ride.exempt: true
survivalplus.lootchests: true
survivalplus.day: true
survivalplus.night: true
@@ -344,8 +344,6 @@ permissions:
survivalplus.heal.others: true
survivalplus.notify: true
survivalplus.chunkanimals: true
survivalplus.claim.use: true
survivalplus.claim.trust: true
survivalplus.commandblocker.add:
description: Erlaubt das Hinzufügen von Befehlen zur Blockierliste
default: op
@@ -481,12 +479,6 @@ permissions:
survivalplus.nick:
description: Erlaubt das Ändern des eigenen Nicknamens (mit Farben & Hex)
default: op
survivalplus.ride:
description: Erlaubt das Reiten von Spielern
default: op
survivalplus.ride.exempt:
description: Spieler mit dieser Permission können nicht geritten werden
default: op
survivalplus.lootchests:
description: Erlaubt das Verwalten und Teleportieren zu Loot-Kisten
default: op