Upload via GUI (14 Dateien)

This commit is contained in:
2026-11-07 22:45:48 +00:00
parent 6be4f77ddb
commit c2ce2764f8
7 changed files with 549 additions and 377 deletions
@@ -80,7 +80,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
}
case "create" -> {
if (args.length < 3) {
plugin.send(p, "&cNutzung: /chunkinfo pvp create <Name>");
plugin.send(p, "&cNutzung: /chunks pvp create <Name>");
return;
}
String err = plugin.pvp.create(p, args[2]);
@@ -88,7 +88,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
}
case "delete" -> {
if (args.length < 3) {
plugin.send(p, "&cNutzung: /chunkinfo pvp delete <Name>");
plugin.send(p, "&cNutzung: /chunks pvp delete <Name>");
return;
}
plugin.send(p, plugin.pvp.delete(args[2]) ? "&aRegion geloescht." : "&cRegion nicht gefunden.");
@@ -100,7 +100,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
plugin.pvp.all().values().forEach(r -> p.sendMessage(plugin.line("&8- &e" + r.name() + " &7(" + r.world()
+ ": " + r.minX() + "," + r.minZ() + " bis " + r.maxX() + "," + r.maxZ() + ")")));
}
default -> plugin.send(p, "&7/chunkinfo pvp pos1 | pos2 | create <Name> | delete <Name> | list");
default -> plugin.send(p, "&7/chunks pvp pos1 | pos2 | create <Name> | delete <Name> | list");
}
}
@@ -155,7 +155,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
for (Land land : shown) {
describe(p, land);
}
p.sendMessage(plugin.line("&8Mehr: &7/chunkinfo top | skip | gift | queue | watch | temp"));
p.sendMessage(plugin.line("&8Mehr: &7/chunks top | skip | gift | queue | watch | temp"));
}
private void describe(Player p, Land land) {
@@ -187,7 +187,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
p.sendMessage(plugin.line(sb + (locked.size() > 5 ? " ..." : "")));
}
if (plugin.getConfig().getBoolean("skip.enabled", true)) {
p.sendMessage(plugin.line("&7Sofort freischalten: &e/chunkinfo skip &7(Preis: &e"
p.sendMessage(plugin.line("&7Sofort freischalten: &e/chunks skip &7(Preis: &e"
+ price(first - now) + "&7)"));
}
}
@@ -253,7 +253,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
return Math.max(min, remainingMs / 3_600_000.0 * perHour);
}
/** /chunkinfo skip [Land] [confirm] bzw. /chunkinfo gift <Land> [confirm] */
/** /chunks skip [Land] [confirm] bzw. /chunks gift <Land> [confirm] */
private void skip(Player p, String[] args, boolean gift) {
if (!allowed(p, gift ? "gift" : "skip")) {
return;
@@ -270,7 +270,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
if (name != null) {
land = findLand(name);
} else if (gift) {
plugin.send(p, "&cNutzung: /chunkinfo gift <Land> [confirm]");
plugin.send(p, "&cNutzung: /chunks gift <Land> [confirm]");
return;
} else {
land = plugin.landsOf(p.getUniqueId()).stream()
@@ -304,7 +304,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
if (!confirm) {
plugin.send(p, "&7Slot in &e" + land.getName() + " &7(noch &e" + TimeUtil.format(remaining)
+ "&7) freischalten fuer &e" + money(price) + (fromLand ? " &7aus der Landkasse" : "")
+ "&7. Bestaetigen: &e/chunkinfo " + (gift ? "gift " : "skip ") + land.getName() + " confirm");
+ "&7. Bestaetigen: &e/chunks " + (gift ? "gift " : "skip ") + land.getName() + " confirm");
return;
}
@@ -361,12 +361,12 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
int cx = p.getLocation().getBlockX() >> 4;
int cz = p.getLocation().getBlockZ() >> 4;
if (plugin.lands.getLandByUnloadedChunk(w, cx, cz) != null) {
plugin.send(p, "&cDieser Chunk ist schon geclaimt. Mit &e/chunkinfo watch &ckannst du ihn beobachten.");
plugin.send(p, "&cDieser Chunk ist schon geclaimt. Mit &e/chunks watch &ckannst du ihn beobachten.");
return;
}
List<Entry> mine = plugin.store.entries(Kind.QUEUE).stream().filter(e -> e.player().equals(p.getUniqueId())).toList();
if (mine.size() >= plugin.getConfig().getInt("queue.max", 3)) {
plugin.send(p, "&cDeine Warteschlange ist voll (&e/chunkinfo queue clear&c).");
plugin.send(p, "&cDeine Warteschlange ist voll (&e/chunks queue clear&c).");
return;
}
if (mine.stream().anyMatch(e -> e.at(w.getName(), cx, cz))) {
@@ -400,12 +400,12 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
int cz = p.getLocation().getBlockZ() >> 4;
Land holder = plugin.lands.getLandByUnloadedChunk(w, cx, cz);
if (holder == null) {
plugin.send(p, "&cDieser Chunk ist frei - du kannst ihn direkt claimen oder mit &e/chunkinfo queue &avormerken.");
plugin.send(p, "&cDieser Chunk ist frei - du kannst ihn direkt claimen oder mit &e/chunks queue &avormerken.");
return;
}
List<Entry> mine = plugin.store.entries(Kind.WATCH).stream().filter(e -> e.player().equals(p.getUniqueId())).toList();
if (mine.size() >= plugin.getConfig().getInt("watch.max", 5)) {
plugin.send(p, "&cDeine Watchlist ist voll (&e/chunkinfo watch clear&c).");
plugin.send(p, "&cDeine Watchlist ist voll (&e/chunks watch clear&c).");
return;
}
if (mine.stream().anyMatch(e -> e.at(w.getName(), cx, cz))) {
@@ -469,7 +469,7 @@ final class InfoCommand implements CommandExecutor, TabCompleter {
try {
days = Integer.parseInt(arg);
} catch (NumberFormatException ex) {
plugin.send(p, "&cNutzung: /chunkinfo temp <Tage> | cancel | list");
plugin.send(p, "&cNutzung: /chunks temp <Tage> | cancel | list");
return;
}
int maxDays = plugin.getConfig().getInt("temp-claims.max-days", 30);
@@ -67,17 +67,17 @@ public final class LandsChunkInfo extends JavaPlugin implements Listener {
vault = VaultHook.create();
}
if (vault == null) {
getLogger().info("Vault/Economy nicht gefunden: Bezahlung nur aus der Landkasse moeglich, /chunkinfo gift ist deaktiviert.");
getLogger().info("Vault/Economy nicht gefunden: Bezahlung nur aus der Landkasse moeglich, /chunks gift ist deaktiviert.");
}
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new ChunkExpansion(this).register();
}
pvp = new PvpRegions(getDataFolder(), getLogger());
pvp = new PvpRegions(this, getDataFolder(), getLogger());
Bukkit.getPluginManager().registerEvents(pvp, this);
Bukkit.getPluginManager().registerEvents(this, this);
InfoCommand command = new InfoCommand(this);
var cmd = getCommand("chunkinfo");
var cmd = getCommand("chunks");
if (cmd != null) {
cmd.setExecutor(command);
cmd.setTabCompleter(command);
@@ -87,6 +87,9 @@ public final class LandsChunkInfo extends JavaPlugin implements Listener {
@Override
public void onDisable() {
if (pvp != null) {
pvp.shutdown();
}
if (store != null) {
store.save();
}
@@ -374,7 +377,7 @@ public final class LandsChunkInfo extends JavaPlugin implements Listener {
if (lands.getLandByUnloadedChunk(w, e.x(), e.z()) == null) {
store.remove(Kind.WATCH, e);
send(p, "&aDer beobachtete Chunk &e" + e.x() + ", " + e.z() + " &a(" + e.world()
+ ") ist jetzt frei! Mit &e/chunkinfo queue &akannst du ihn dir sichern.");
+ ") ist jetzt frei! Mit &e/chunks queue &akannst du ihn dir sichern.");
}
}
}
+115 -3
View File
@@ -10,6 +10,15 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerVelocityEvent;
import org.bukkit.event.entity.EntityKnockbackByEntityEvent;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.IOException;
@@ -36,13 +45,20 @@ final class PvpRegions implements Listener {
}
}
private static final String BYPASS = "lands.bypass.attack_player";
/** Spieler, die gerade in einer PvP-Region getroffen wurden (fuer den Rueckstoss). */
private final Map<UUID, Long> recentHit = new ConcurrentHashMap<>();
private final JavaPlugin plugin;
private final Map<UUID, PermissionAttachment> bypass = new ConcurrentHashMap<>();
private final File file;
private final Logger log;
private final Map<String, Region> regions = new LinkedHashMap<>();
private final Map<UUID, Location> pos1 = new ConcurrentHashMap<>();
private final Map<UUID, Location> pos2 = new ConcurrentHashMap<>();
PvpRegions(File dataFolder, Logger log) {
PvpRegions(JavaPlugin plugin, File dataFolder, Logger log) {
this.plugin = plugin;
this.file = new File(dataFolder, "pvp-regions.yml");
this.log = log;
load();
@@ -100,7 +116,7 @@ final class PvpRegions implements Listener {
Location a = pos1.get(p.getUniqueId());
Location b = pos2.get(p.getUniqueId());
if (a == null || b == null) {
return "Setze zuerst &e/chunkinfo pvp pos1 &7und &e/chunkinfo pvp pos2&7.";
return "Setze zuerst &e/chunks pvp pos1 &7und &e/chunks pvp pos2&7.";
}
if (a.getWorld() == null || !a.getWorld().equals(b.getWorld())) {
return "Beide Punkte muessen in derselben Welt liegen.";
@@ -109,6 +125,7 @@ final class PvpRegions implements Listener {
Math.min(a.getBlockX(), b.getBlockX()), Math.min(a.getBlockZ(), b.getBlockZ()),
Math.max(a.getBlockX(), b.getBlockX()), Math.max(a.getBlockZ(), b.getBlockZ())));
save();
refreshAll();
return null;
}
@@ -116,6 +133,7 @@ final class PvpRegions implements Listener {
boolean removed = regions.remove(name.toLowerCase()) != null;
if (removed) {
save();
refreshAll();
}
return removed;
}
@@ -137,6 +155,77 @@ final class PvpRegions implements Listener {
return false;
}
// ------------------------------------------------- Lands-Bypass in Regionen
/** Gibt dem Spieler in einer PvP-Region das Lands-Recht zum Angreifen, sonst entzieht es es. */
void refresh(Player p) {
refreshAt(p, p.isOnline() && inRegion(p.getLocation()));
}
void refreshAll() {
for (Player p : Bukkit.getOnlinePlayers()) {
refresh(p);
}
}
void shutdown() {
for (Player p : Bukkit.getOnlinePlayers()) {
PermissionAttachment att = bypass.remove(p.getUniqueId());
if (att != null) {
try {
p.removeAttachment(att);
} catch (IllegalArgumentException ignored) {
// schon entfernt
}
}
}
}
@EventHandler
public void onMove(PlayerMoveEvent e) {
if (e.getTo() == null || regions.isEmpty() && bypass.isEmpty()) {
return;
}
if (e.getFrom().getBlockX() != e.getTo().getBlockX() || e.getFrom().getBlockZ() != e.getTo().getBlockZ()) {
boolean inside = inRegion(e.getTo());
if (inside != bypass.containsKey(e.getPlayer().getUniqueId())) {
refreshAt(e.getPlayer(), inside);
}
}
}
@EventHandler
public void onTeleport(PlayerTeleportEvent e) {
refreshAt(e.getPlayer(), inRegion(e.getTo()));
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
refresh(e.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
bypass.remove(e.getPlayer().getUniqueId());
recentHit.remove(e.getPlayer().getUniqueId());
}
private void refreshAt(Player p, boolean inside) {
PermissionAttachment att = bypass.get(p.getUniqueId());
if (inside && att == null) {
PermissionAttachment a = p.addAttachment(plugin);
a.setPermission(BYPASS, true);
bypass.put(p.getUniqueId(), a);
} else if (!inside && att != null) {
bypass.remove(p.getUniqueId());
try {
p.removeAttachment(att);
} catch (IllegalArgumentException ignored) {
// schon entfernt
}
}
}
// ----------------------------------------------------------------- Event
/**
@@ -145,7 +234,7 @@ final class PvpRegions implements Listener {
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onDamage(EntityDamageByEntityEvent e) {
if (!e.isCancelled() || regions.isEmpty() || !(e.getEntity() instanceof Player victim)) {
if (regions.isEmpty() || !(e.getEntity() instanceof Player victim)) {
return;
}
Entity damager = e.getDamager();
@@ -156,7 +245,30 @@ final class PvpRegions implements Listener {
return;
}
if (inRegion(attacker.getLocation()) && inRegion(victim.getLocation())) {
recentHit.put(victim.getUniqueId(), System.currentTimeMillis());
if (e.isCancelled()) {
e.setCancelled(false);
}
}
}
/** Rueckstoss in der PvP-Region freigeben, falls Lands ihn fuer Land-Mitglieder unterdrueckt. */
@EventHandler(priority = EventPriority.HIGHEST)
public void onKnockback(EntityKnockbackByEntityEvent e) {
if (e.isCancelled() && e.getEntity() instanceof Player victim && isRecent(victim)) {
e.setCancelled(false);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onVelocity(PlayerVelocityEvent e) {
if (e.isCancelled() && isRecent(e.getPlayer())) {
e.setCancelled(false);
}
}
private boolean isRecent(Player victim) {
Long t = recentHit.get(victim.getUniqueId());
return t != null && System.currentTimeMillis() - t < 500 && inRegion(victim.getLocation());
}
}