Upload via GUI (6 Dateien)
This commit is contained in:
@@ -1,2 +1,102 @@
|
|||||||
# LandHopperLimit
|
# LandHopperLimit
|
||||||
|
|
||||||
|
Dieses Plugin begrenzt, wie viele **Trichter** (Hopper) ein **Land** haben darf.
|
||||||
|
Standard: **300 Trichter pro Land**. Ist das Limit erreicht, kann niemand mehr einen neuen Trichter in dem Land setzen.
|
||||||
|
|
||||||
|
Bereits gesetzte Trichter bleiben immer stehen, es wird nichts gelöscht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Was du brauchst
|
||||||
|
|
||||||
|
- Einen **Paper-Server** (Version 1.21.4)
|
||||||
|
- Das Plugin **Lands**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation (5 Schritte)
|
||||||
|
|
||||||
|
1. Lade die Datei `LandHopperLimit-1.0.0.jar` herunter.
|
||||||
|
2. Kopiere sie in den Ordner `plugins` deines Servers.
|
||||||
|
3. Starte den Server neu.
|
||||||
|
4. Gib einmal im Spiel (oder in der Konsole) diesen Befehl ein:
|
||||||
|
```
|
||||||
|
/lhl scan
|
||||||
|
```
|
||||||
|
Das zählt alle schon vorhandenen Trichter. Das dauert je nach Servergröße ein paar Minuten. Warte, bis "Scan fertig" erscheint.
|
||||||
|
5. Fertig!
|
||||||
|
|
||||||
|
Den Scan musst du nur **einmal** machen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Befehle
|
||||||
|
|
||||||
|
| Befehl | Was er macht | Wer darf das |
|
||||||
|
|---|---|---|
|
||||||
|
| `/lhl` oder `/lhl info` | Zeigt, wie viele Trichter das Land hat, in dem du stehst | Alle |
|
||||||
|
| `/lhl scan` | Zählt alle Trichter aller Länder neu | Nur Admins |
|
||||||
|
| `/lhl reload` | Lädt die Einstellungen neu | Nur Admins |
|
||||||
|
|
||||||
|
`/lhl` kannst du auch als `/landhopperlimit` oder `/trichterlimit` schreiben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rechte (Permissions)
|
||||||
|
|
||||||
|
| Recht | Was es bewirkt | Standard |
|
||||||
|
|---|---|---|
|
||||||
|
| `landhopperlimit.info` | `/lhl info` benutzen | Alle |
|
||||||
|
| `landhopperlimit.admin` | `/lhl scan` und `/lhl reload` benutzen | Nur OPs |
|
||||||
|
| `landhopperlimit.bypass` | Das Limit ignorieren | Niemand |
|
||||||
|
|
||||||
|
Wer `landhopperlimit.bypass` bekommt, darf unbegrenzt Trichter setzen (zum Beispiel für Teammitglieder).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Einstellungen ändern
|
||||||
|
|
||||||
|
Öffne die Datei `plugins/LandHopperLimit/config.yml` mit einem Texteditor.
|
||||||
|
|
||||||
|
**Limit ändern:**
|
||||||
|
```yaml
|
||||||
|
limit: 300
|
||||||
|
```
|
||||||
|
Ersetze `300` durch die Zahl, die du willst.
|
||||||
|
|
||||||
|
**Nur bestimmte Welten:**
|
||||||
|
```yaml
|
||||||
|
worlds:
|
||||||
|
- world
|
||||||
|
- world_the_end
|
||||||
|
```
|
||||||
|
Lässt du die Liste leer (`worlds: []`), gilt das Limit in **allen** Welten.
|
||||||
|
|
||||||
|
**Nachrichten ändern:** Alle Texte stehen unter `messages:`. Farben schreibst du mit `&`, zum Beispiel `&c` für rot und `&e` für gelb.
|
||||||
|
|
||||||
|
Nach jeder Änderung im Spiel `/lhl reload` eingeben. Ein Neustart ist nicht nötig.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gut zu wissen
|
||||||
|
|
||||||
|
- Das Limit gilt für **alle** Trichter, auch für spezielle Trichter von anderen Plugins, solange sie Trichter-Blöcke sind.
|
||||||
|
- Steht man **außerhalb eines Lands** (Wildnis), gibt es kein Limit.
|
||||||
|
- Die Zahlen werden in der Datei `plugins/LandHopperLimit/hopper-counts.yml` gespeichert. Diese Datei bitte **nicht von Hand ändern**.
|
||||||
|
- Wurden Trichter mit anderen Plugins oder Tools gesetzt (zum Beispiel WorldEdit), kann die Zahl kurz falsch sein. Mit `/lhl scan` stimmt sie wieder.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hilfe, es klappt nicht!
|
||||||
|
|
||||||
|
| Problem | Lösung |
|
||||||
|
|---|---|
|
||||||
|
| Plugin startet nicht | Prüfe, ob **Lands** installiert ist und der Server **Paper 1.21.4** ist |
|
||||||
|
| "Du stehst in keinem Land" | Stell dich in ein Land, in der Wildnis gibt es nichts zu zählen |
|
||||||
|
| Die Zahl stimmt nicht | `/lhl scan` ausführen |
|
||||||
|
| "Dafür hast du keine Rechte" | Du brauchst das Recht `landhopperlimit.admin` |
|
||||||
|
| Ein Spieler soll das Limit umgehen | Gib ihm das Recht `landhopperlimit.bypass` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Von Lukas (Viper Network)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.vipernetwork</groupId>
|
||||||
|
<artifactId>LandHopperLimit</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>21</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<repositories>
|
||||||
|
<repository><id>papermc</id><url>https://repo.papermc.io/repository/maven-public/</url></repository>
|
||||||
|
<repository><id>jitpack</id><url>https://jitpack.io</url></repository>
|
||||||
|
</repositories>
|
||||||
|
<dependencies>
|
||||||
|
<dependency><groupId>io.papermc.paper</groupId><artifactId>paper-api</artifactId><version>1.21.4-R0.1-SNAPSHOT</version><scope>provided</scope></dependency>
|
||||||
|
<dependency><groupId>com.github.angeschossen</groupId><artifactId>LandsAPI</artifactId><version>7.25.4</version><scope>provided</scope></dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<finalName>LandHopperLimit-${project.version}</finalName>
|
||||||
|
<resources><resource><directory>src/main/resources</directory><filtering>true</filtering></resource></resources>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package de.vipernetwork.landhopperlimit;
|
||||||
|
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert die Anzahl der Trichter pro Chunk, damit auch nicht geladene Chunks
|
||||||
|
* eines Lands mitgezählt werden können, ohne sie laden zu müssen.
|
||||||
|
*/
|
||||||
|
public final class ChunkCache {
|
||||||
|
|
||||||
|
private final Map<UUID, Map<Long, Integer>> counts = new ConcurrentHashMap<>();
|
||||||
|
private final File file;
|
||||||
|
private final Logger logger;
|
||||||
|
private volatile boolean dirty;
|
||||||
|
|
||||||
|
public ChunkCache(File file, Logger logger) {
|
||||||
|
this.file = file;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long key(int x, int z) {
|
||||||
|
return ((long) x << 32) | (z & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(UUID world, int x, int z) {
|
||||||
|
Map<Long, Integer> map = counts.get(world);
|
||||||
|
if (map == null) return 0;
|
||||||
|
return map.getOrDefault(key(x, z), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(UUID world, int x, int z, int amount) {
|
||||||
|
Map<Long, Integer> map = counts.computeIfAbsent(world, w -> new ConcurrentHashMap<>());
|
||||||
|
long k = key(x, z);
|
||||||
|
Integer old = amount <= 0 ? map.remove(k) : map.put(k, amount);
|
||||||
|
if (old == null ? amount > 0 : old != amount) dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(UUID world, int x, int z, int delta) {
|
||||||
|
set(world, x, z, Math.max(0, get(world, x, z) + delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zählt die Trichter in einem geladenen Chunk. Der Block "exclude" wird nicht mitgezählt. */
|
||||||
|
public static int countHoppers(Chunk chunk, Block exclude) {
|
||||||
|
int n = 0;
|
||||||
|
for (BlockState state : chunk.getTileEntities(false)) {
|
||||||
|
if (state.getType() != Material.HOPPER) continue;
|
||||||
|
if (exclude != null && state.getX() == exclude.getX() && state.getY() == exclude.getY()
|
||||||
|
&& state.getZ() == exclude.getZ()) continue;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zählt einen geladenen Chunk neu und speichert das Ergebnis. */
|
||||||
|
public int recount(Chunk chunk, Block exclude) {
|
||||||
|
int n = countHoppers(chunk, exclude);
|
||||||
|
set(chunk.getWorld().getUID(), chunk.getX(), chunk.getZ(), n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
if (!file.exists()) return;
|
||||||
|
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||||
|
ConfigurationSection worlds = yaml.getConfigurationSection("worlds");
|
||||||
|
if (worlds == null) return;
|
||||||
|
for (String worldId : worlds.getKeys(false)) {
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = UUID.fromString(worldId);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<Long, Integer> map = counts.computeIfAbsent(uuid, w -> new ConcurrentHashMap<>());
|
||||||
|
for (String entry : worlds.getStringList(worldId)) {
|
||||||
|
String[] p = entry.split(",");
|
||||||
|
if (p.length != 3) continue;
|
||||||
|
try {
|
||||||
|
int amount = Integer.parseInt(p[2].trim());
|
||||||
|
if (amount > 0) map.put(key(Integer.parseInt(p[0].trim()), Integer.parseInt(p[1].trim())), amount);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Erstellt eine Kopie der Daten (auf dem Main-Thread aufrufen), die dann async gespeichert werden kann. */
|
||||||
|
public Map<UUID, List<String>> snapshot() {
|
||||||
|
Map<UUID, List<String>> snap = new HashMap<>();
|
||||||
|
counts.forEach((world, map) -> {
|
||||||
|
List<String> list = new ArrayList<>(map.size());
|
||||||
|
map.forEach((k, v) -> list.add((int) (k >> 32) + "," + (int) (long) k + "," + v));
|
||||||
|
snap.put(world, list);
|
||||||
|
});
|
||||||
|
dirty = false;
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void write(Map<UUID, List<String>> snap) {
|
||||||
|
YamlConfiguration yaml = new YamlConfiguration();
|
||||||
|
yaml.options().setHeader(List.of("Automatisch erstellt - nicht von Hand bearbeiten.",
|
||||||
|
"Format: chunkX,chunkZ,anzahlTrichter"));
|
||||||
|
snap.forEach((world, list) -> yaml.set("worlds." + world, list));
|
||||||
|
try {
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
yaml.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warning("Konnte " + file.getName() + " nicht speichern: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package de.vipernetwork.landhopperlimit;
|
||||||
|
|
||||||
|
import me.angeschossen.lands.api.LandsIntegration;
|
||||||
|
import me.angeschossen.lands.api.land.ChunkCoordinate;
|
||||||
|
import me.angeschossen.lands.api.land.Land;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
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.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockExplodeEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||||
|
import org.bukkit.event.world.ChunkLoadEvent;
|
||||||
|
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public final class LandHopperLimit extends JavaPlugin implements Listener, TabCompleter {
|
||||||
|
|
||||||
|
private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand();
|
||||||
|
|
||||||
|
private LandsIntegration lands;
|
||||||
|
private ChunkCache cache;
|
||||||
|
private int limit;
|
||||||
|
private Set<String> worlds;
|
||||||
|
private boolean scanning;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
saveDefaultConfig();
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
|
lands = LandsIntegration.of(this);
|
||||||
|
cache = new ChunkCache(new File(getDataFolder(), "hopper-counts.yml"), getLogger());
|
||||||
|
cache.load();
|
||||||
|
|
||||||
|
// Bereits geladene Chunks direkt zählen
|
||||||
|
for (World world : Bukkit.getWorlds()) {
|
||||||
|
if (!worldEnabled(world)) continue;
|
||||||
|
for (Chunk chunk : world.getLoadedChunks()) cache.recount(chunk, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServer().getPluginManager().registerEvents(this, this);
|
||||||
|
var cmd = getCommand("landhopperlimit");
|
||||||
|
if (cmd != null) {
|
||||||
|
cmd.setExecutor(this);
|
||||||
|
cmd.setTabCompleter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle 5 Minuten speichern, falls sich etwas geändert hat
|
||||||
|
Bukkit.getScheduler().runTaskTimer(this, this::saveAsync, 20L * 300, 20L * 300);
|
||||||
|
getLogger().info("Limit: " + limit + " Trichter pro Land.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
if (cache != null) cache.write(cache.snapshot());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings() {
|
||||||
|
reloadConfig();
|
||||||
|
limit = getConfig().getInt("limit", 300);
|
||||||
|
worlds = new HashSet<>(getConfig().getStringList("worlds"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAsync() {
|
||||||
|
if (!cache.isDirty()) return;
|
||||||
|
Map<UUID, List<String>> snap = cache.snapshot();
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(this, () -> cache.write(snap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean worldEnabled(World world) {
|
||||||
|
return worlds.isEmpty() || worlds.contains(world.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Zählen
|
||||||
|
|
||||||
|
/** Zählt alle Trichter eines Lands. Geladene Chunks werden live gezählt, die anderen kommen aus dem Cache. */
|
||||||
|
private int countLand(Land land, Block exclude) {
|
||||||
|
int total = 0;
|
||||||
|
for (World world : Bukkit.getWorlds()) {
|
||||||
|
Collection<ChunkCoordinate> chunks = land.getChunks(world);
|
||||||
|
if (chunks == null || chunks.isEmpty()) continue;
|
||||||
|
UUID uid = world.getUID();
|
||||||
|
for (ChunkCoordinate cc : chunks) {
|
||||||
|
int x = cc.getX(), z = cc.getZ();
|
||||||
|
if (world.isChunkLoaded(x, z)) {
|
||||||
|
Block ex = exclude != null && exclude.getWorld().equals(world)
|
||||||
|
&& (exclude.getX() >> 4) == x && (exclude.getZ() >> 4) == z ? exclude : null;
|
||||||
|
total += cache.recount(world.getChunkAt(x, z), ex);
|
||||||
|
} else {
|
||||||
|
total += cache.get(uid, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Events
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
public void onPlaceCheck(BlockPlaceEvent event) {
|
||||||
|
Block block = event.getBlockPlaced();
|
||||||
|
if (block.getType() != Material.HOPPER) return;
|
||||||
|
if (!worldEnabled(block.getWorld())) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.hasPermission("landhopperlimit.bypass")) return;
|
||||||
|
|
||||||
|
Land land = lands.getLandByChunk(block.getWorld(), block.getX() >> 4, block.getZ() >> 4);
|
||||||
|
if (land == null) return; // Wildnis - kein Land-Limit
|
||||||
|
|
||||||
|
int count = countLand(land, block);
|
||||||
|
if (count >= limit) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
send(player, "messages.limit-reached", land.getName(), count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onPlaced(BlockPlaceEvent event) {
|
||||||
|
Block b = event.getBlockPlaced();
|
||||||
|
if (b.getType() == Material.HOPPER) cache.add(b.getWorld().getUID(), b.getX() >> 4, b.getZ() >> 4, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onBreak(BlockBreakEvent event) {
|
||||||
|
removed(event.getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onEntityExplode(EntityExplodeEvent event) {
|
||||||
|
event.blockList().forEach(this::removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onBlockExplode(BlockExplodeEvent event) {
|
||||||
|
event.blockList().forEach(this::removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removed(Block b) {
|
||||||
|
if (b.getType() == Material.HOPPER) cache.add(b.getWorld().getUID(), b.getX() >> 4, b.getZ() >> 4, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onChunkLoad(ChunkLoadEvent event) {
|
||||||
|
if (!worldEnabled(event.getWorld())) return;
|
||||||
|
cache.recount(event.getChunk(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||||
|
if (!worldEnabled(event.getWorld())) return;
|
||||||
|
cache.recount(event.getChunk(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Befehle
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
|
String sub = args.length > 0 ? args[0].toLowerCase() : "info";
|
||||||
|
switch (sub) {
|
||||||
|
case "info" -> {
|
||||||
|
if (!(sender instanceof Player p)) {
|
||||||
|
sender.sendMessage("Nur ingame nutzbar.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!p.hasPermission("landhopperlimit.info")) {
|
||||||
|
send(p, "messages.no-permission", null, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Land land = lands.getLandByChunk(p.getWorld(), p.getLocation().getBlockX() >> 4, p.getLocation().getBlockZ() >> 4);
|
||||||
|
if (land == null) {
|
||||||
|
send(p, "messages.no-land", null, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
send(p, "messages.info", land.getName(), countLand(land, null));
|
||||||
|
}
|
||||||
|
case "scan" -> {
|
||||||
|
if (!sender.hasPermission("landhopperlimit.admin")) {
|
||||||
|
send(sender, "messages.no-permission", null, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
startScan(sender);
|
||||||
|
}
|
||||||
|
case "reload" -> {
|
||||||
|
if (!sender.hasPermission("landhopperlimit.admin")) {
|
||||||
|
send(sender, "messages.no-permission", null, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
loadSettings();
|
||||||
|
send(sender, "messages.reloaded", null, 0);
|
||||||
|
}
|
||||||
|
default -> sender.sendMessage(LEGACY.deserialize("&7/" + label + " info &8| &7scan &8| &7reload"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||||
|
if (args.length != 1) return List.of();
|
||||||
|
List<String> options = sender.hasPermission("landhopperlimit.admin") ? List.of("info", "scan", "reload") : List.of("info");
|
||||||
|
return options.stream().filter(s -> s.startsWith(args[0].toLowerCase())).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lädt alle Chunks aller Länder nacheinander und zählt die Trichter. Nur einmal nach der Installation nötig. */
|
||||||
|
private void startScan(CommandSender sender) {
|
||||||
|
if (scanning) {
|
||||||
|
send(sender, "messages.scan-running", null, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Deque<long[]> queue = new ArrayDeque<>();
|
||||||
|
List<World> worldList = Bukkit.getWorlds();
|
||||||
|
for (Land land : lands.getLands()) {
|
||||||
|
for (int i = 0; i < worldList.size(); i++) {
|
||||||
|
Collection<ChunkCoordinate> chunks = land.getChunks(worldList.get(i));
|
||||||
|
if (chunks == null) continue;
|
||||||
|
for (ChunkCoordinate cc : chunks) queue.add(new long[]{i, cc.getX(), cc.getZ()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int total = queue.size();
|
||||||
|
if (total == 0) {
|
||||||
|
sender.sendMessage(LEGACY.deserialize(msg("messages.scan-done").replace("{chunks}", "0")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scanning = true;
|
||||||
|
sender.sendMessage(LEGACY.deserialize(msg("messages.scan-start").replace("{chunks}", String.valueOf(total))));
|
||||||
|
|
||||||
|
AtomicInteger done = new AtomicInteger();
|
||||||
|
// Pro Tick ein paar Chunks async laden, damit der Server nicht laggt
|
||||||
|
Bukkit.getScheduler().runTaskTimer(this, task -> {
|
||||||
|
for (int n = 0; n < 4 && !queue.isEmpty(); n++) {
|
||||||
|
long[] e = queue.poll();
|
||||||
|
World world = worldList.get((int) e[0]);
|
||||||
|
int x = (int) e[1], z = (int) e[2];
|
||||||
|
if (world.isChunkLoaded(x, z)) {
|
||||||
|
cache.recount(world.getChunkAt(x, z), null);
|
||||||
|
progress(sender, done.incrementAndGet(), total);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
world.getChunkAtAsync(x, z, false).handle((chunk, error) -> {
|
||||||
|
if (chunk != null && error == null) cache.recount(chunk, null);
|
||||||
|
progress(sender, done.incrementAndGet(), total);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (queue.isEmpty()) task.cancel();
|
||||||
|
}, 1L, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void progress(CommandSender sender, int done, int total) {
|
||||||
|
if (done % 500 == 0 && done < total) {
|
||||||
|
sender.sendMessage(LEGACY.deserialize(msg("messages.scan-progress")
|
||||||
|
.replace("{done}", String.valueOf(done)).replace("{chunks}", String.valueOf(total))));
|
||||||
|
}
|
||||||
|
if (done == total) {
|
||||||
|
scanning = false;
|
||||||
|
saveAsync();
|
||||||
|
sender.sendMessage(LEGACY.deserialize(msg("messages.scan-done").replace("{chunks}", String.valueOf(total))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Nachrichten
|
||||||
|
|
||||||
|
private String msg(String path) {
|
||||||
|
return getConfig().getString("messages.prefix", "") + getConfig().getString(path, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(CommandSender to, String path, String land, int count) {
|
||||||
|
String text = msg(path)
|
||||||
|
.replace("{limit}", String.valueOf(limit))
|
||||||
|
.replace("{count}", String.valueOf(count))
|
||||||
|
.replace("{land}", land == null ? "-" : land);
|
||||||
|
Component c = LEGACY.deserialize(text);
|
||||||
|
to.sendMessage(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# LandHopperLimit
|
||||||
|
# Begrenzt ALLE Trichter (normale + upgradebare) pro Land.
|
||||||
|
# Bereits gesetzte Trichter bleiben stehen. Ist ein Land schon über dem Limit,
|
||||||
|
# können dort nur keine neuen Trichter gesetzt werden.
|
||||||
|
|
||||||
|
# Maximale Anzahl Trichter pro Land
|
||||||
|
limit: 300
|
||||||
|
|
||||||
|
# Welten, in denen das Limit gilt. Leer lassen = alle Welten.
|
||||||
|
# Beispiel:
|
||||||
|
# worlds:
|
||||||
|
# - world
|
||||||
|
# - world_the_end
|
||||||
|
worlds: []
|
||||||
|
|
||||||
|
# Farbcodes mit & (z.B. &c = rot, &e = gelb)
|
||||||
|
# Platzhalter: {limit}, {count}, {land}, {chunks}, {done}
|
||||||
|
messages:
|
||||||
|
prefix: '&8[&6Trichter&8] '
|
||||||
|
limit-reached: '&cDas Land &e{land} &chat das Maximum von &e{limit} &cTrichtern erreicht.'
|
||||||
|
info: '&7Land &e{land}&7: &e{count}&7/&e{limit} &7Trichter'
|
||||||
|
no-land: '&cDu stehst in keinem Land.'
|
||||||
|
no-permission: '&cDafür hast du keine Rechte.'
|
||||||
|
reloaded: '&aConfig neu geladen.'
|
||||||
|
scan-start: '&7Scanne &e{chunks} &7Chunks aller Länder...'
|
||||||
|
scan-progress: '&7Scan: &e{done}&7/&e{chunks} &7Chunks'
|
||||||
|
scan-done: '&aScan fertig! &e{chunks} &aChunks gezählt.'
|
||||||
|
scan-running: '&cEin Scan läuft bereits.'
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
name: LandHopperLimit
|
||||||
|
version: '${project.version}'
|
||||||
|
main: de.vipernetwork.landhopperlimit.LandHopperLimit
|
||||||
|
api-version: '1.20'
|
||||||
|
author: Lukas (Viper Network)
|
||||||
|
description: Begrenzt die Anzahl aller Trichter pro Lands-Land.
|
||||||
|
depend: [Lands]
|
||||||
|
commands:
|
||||||
|
landhopperlimit:
|
||||||
|
description: Trichter-Limit pro Land
|
||||||
|
usage: /<command> [info|scan|reload]
|
||||||
|
aliases: [lhl, trichterlimit]
|
||||||
|
permissions:
|
||||||
|
landhopperlimit.info:
|
||||||
|
description: Zeigt die Trichter-Anzahl im aktuellen Land
|
||||||
|
default: true
|
||||||
|
landhopperlimit.admin:
|
||||||
|
description: Darf scan und reload nutzen
|
||||||
|
default: op
|
||||||
|
landhopperlimit.bypass:
|
||||||
|
description: Ignoriert das Limit beim Platzieren
|
||||||
|
default: false
|
||||||
Reference in New Issue
Block a user