replaced single commands with /pixelblocks root command and permissions

New /pixelblocks (alias /pb) with create, give, exit and destroyall
subcommands, each gated by a permission (exit defaults to true, the
rest to op). Fixes the args[0] crash and missing UUID validation in
give, and the offline-owner NPE plus concurrent-modification risk in
destroyall (destroy now takes a force flag that skips the ownership
check for admins). Also centralizes the item id tag in
PixelBlockItem.setBlockId and drops the overridden itemName leftover.
This commit is contained in:
2026-07-23 22:57:48 +02:00
parent bc43519077
commit 76ddfc95af
15 changed files with 277 additions and 142 deletions
@@ -3,10 +3,8 @@ package eu.mhsl.minecraft.pixelblocks;
import co.aikar.taskchain.BukkitTaskChainFactory;
import co.aikar.taskchain.TaskChain;
import co.aikar.taskchain.TaskChainFactory;
import eu.mhsl.minecraft.pixelblocks.commands.CreatePixelBlockCommand;
import eu.mhsl.minecraft.pixelblocks.commands.DestroyPixelBlocksCommand;
import eu.mhsl.minecraft.pixelblocks.commands.ExitWorldCommand;
import eu.mhsl.minecraft.pixelblocks.commands.GivePixelBlockCommand;
import eu.mhsl.minecraft.pixelblocks.commands.PixelBlocksCommand;
import org.bukkit.command.PluginCommand;
import eu.mhsl.minecraft.pixelblocks.listeners.*;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import org.bukkit.Bukkit;
@@ -82,10 +80,10 @@ public final class Main extends JavaPlugin {
getServer().getPluginManager().registerEvents(listener, plugin);
}
Objects.requireNonNull(getCommand("createpixelblock")).setExecutor(new CreatePixelBlockCommand());
Objects.requireNonNull(getCommand("givepixelblock")).setExecutor(new GivePixelBlockCommand());
Objects.requireNonNull(getCommand("exitworld")).setExecutor(new ExitWorldCommand());
Objects.requireNonNull(getCommand("destroypixelblocks")).setExecutor(new DestroyPixelBlocksCommand());
PixelBlocksCommand pixelBlocksCommand = new PixelBlocksCommand();
PluginCommand rootCommand = Objects.requireNonNull(getCommand("pixelblocks"));
rootCommand.setExecutor(pixelBlocksCommand);
rootCommand.setTabCompleter(pixelBlocksCommand);
Bukkit.addRecipe(PixelBlockItem.getRecipe());
}
@@ -36,6 +36,12 @@ public class PixelBlockItem {
}
}
public static void setBlockId(@NotNull ItemStack item, @NotNull UUID id) {
ItemMeta meta = item.getItemMeta();
meta.getPersistentDataContainer().set(idProperty, PersistentDataType.STRING, id.toString());
item.setItemMeta(meta);
}
public static @Nullable BlockInfo getBlockInfo(ItemStack item) {
PersistentDataContainer container = item.getItemMeta().getPersistentDataContainer();
if(!container.has(idProperty)) return null;
@@ -69,7 +75,6 @@ public class PixelBlockItem {
ItemStack item = HeadUtil.getCustomTextureHead(itemTexture);
ItemMeta meta = item.getItemMeta();
meta.setMaxStackSize(1);
meta.itemName(Component.text(emptyBlockUUID.toString()));
meta.displayName(Component.text("Leerer Pixelblock"));
meta.lore(List.of(
Component.text("Der erste Spieler, der den Block platziert wird zum Besitzer des Blocks."),
@@ -1,36 +0,0 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlockWorld;
import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class CreatePixelBlockCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(sender instanceof Player p) {
World playerWorld = p.getWorld();
if(PixelBlockWorld.getPixelBlockWorlds().contains(playerWorld)) {
p.sendMessage("Pixelblöcke können nicht innerhalb anderen Pixelblöcken erstellt werden.");
return true;
}
Location playerLocation = p.getLocation();
PixelBlock.createPixelBlock(
UUID.randomUUID(),
p.getUniqueId(),
playerLocation.toBlockLocation(),
Direction.south
);
}
return true;
}
}
@@ -0,0 +1,32 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlockWorld;
import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class CreateSubCommand extends SubCommand {
public CreateSubCommand() {
super("create", "pixelblocks.command.create", "Erstellt einen neuen Pixelblock an deiner Position.");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
if(PixelBlockWorld.isPixelWorld(player.getWorld())) {
player.sendMessage(Component.text("Pixelblöcke können nicht innerhalb anderer Pixelblöcke erstellt werden.", NamedTextColor.RED));
return;
}
PixelBlock.createPixelBlock(
UUID.randomUUID(),
player.getUniqueId(),
player.getLocation().toBlockLocation(),
Direction.south
);
}
}
@@ -0,0 +1,23 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class DestroyAllSubCommand extends SubCommand {
public DestroyAllSubCommand() {
super("destroyall", "pixelblocks.command.destroyall", "Zerstört alle Pixelblöcke auf dem Server.");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
List<PixelBlock> blocksToDestroy = List.copyOf(Main.pixelBlocks);
blocksToDestroy.forEach(pixelBlock -> pixelBlock.destroy(player, true));
player.sendMessage(Component.text(String.format("%d Pixelblöcke werden zerstört.", blocksToDestroy.size()), NamedTextColor.GREEN));
}
}
@@ -1,19 +0,0 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.Main;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class DestroyPixelBlocksCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(sender instanceof Player p) {
Main.pixelBlocks.forEach(pixelBlock -> pixelBlock.destroy(Bukkit.getPlayer(pixelBlock.getOwnerUUID())));
}
return true;
}
}
@@ -0,0 +1,30 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlockWorld;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ExitSubCommand extends SubCommand {
public ExitSubCommand() {
super("exit", "pixelblocks.command.exit", "Verlässt den Pixelblock, in dem du dich befindest.");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
if(!PixelBlockWorld.isPixelWorld(player.getWorld())) {
player.sendMessage(Component.text("Du befindest dich nicht in einem Pixelblock.", NamedTextColor.RED));
return;
}
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(player.getWorld());
if(pixelBlock == null) {
player.sendMessage(Component.text("Dieser Pixelblock konnte nicht gefunden werden.", NamedTextColor.RED));
return;
}
pixelBlock.exitBlock(player);
}
}
@@ -1,30 +0,0 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlockWorld;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class ExitWorldCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(sender instanceof Player p) {
World playerWorld = p.getWorld();
if(PixelBlockWorld.getOtherWorlds().contains(playerWorld)) {
p.sendMessage("Du kannst nur Pixelblöcke verlassen.");
return true;
}
PixelBlock currentPixelBlock = PixelBlock.getPixelBlockFromBlockWorld(playerWorld);
Objects.requireNonNull(currentPixelBlock);
currentPixelBlock.exitBlock(p);
}
return true;
}
}
@@ -1,40 +0,0 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.PixelBlockItem;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.UUID;
import static eu.mhsl.minecraft.pixelblocks.PixelBlockItem.getEmptyPixelBlock;
public class GivePixelBlockCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(sender instanceof Player p) {
ItemStack result = getEmptyPixelBlock();
ItemMeta itemMeta = result.getItemMeta();
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
if(!dataContainer.has(PixelBlockItem.idProperty)) return false;
String currentId = dataContainer.get(PixelBlockItem.idProperty, PersistentDataType.STRING);
Objects.requireNonNull(currentId);
if(!UUID.fromString(currentId).equals(PixelBlockItem.emptyBlockUUID)) return false;
dataContainer.set(PixelBlockItem.idProperty, PersistentDataType.STRING, args[0]);
result.setItemMeta(itemMeta);
p.getInventory().addItem(result);
}
return true;
}
}
@@ -0,0 +1,44 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import eu.mhsl.minecraft.pixelblocks.PixelBlockItem;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
public class GiveSubCommand extends SubCommand {
public GiveSubCommand() {
super("give", "pixelblocks.command.give", "Gibt dir ein Pixelblock-Item, optional mit fester UUID.");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
UUID blockId;
if(args.length == 0) {
blockId = UUID.randomUUID();
} else {
try {
blockId = UUID.fromString(args[0]);
} catch(IllegalArgumentException e) {
player.sendMessage(Component.text(String.format("'%s' ist keine gültige UUID.", args[0]), NamedTextColor.RED));
return;
}
}
ItemStack item = PixelBlockItem.getEmptyPixelBlock();
PixelBlockItem.setBlockId(item, blockId);
player.getInventory().addItem(item);
player.sendMessage(Component.text(String.format("Pixelblock '%s' erhalten.", blockId), NamedTextColor.GREEN));
}
@Override
public @NotNull List<String> tabComplete(@NotNull Player player, @NotNull String[] args) {
if(args.length == 1) return List.of("<uuid>");
return List.of();
}
}
@@ -0,0 +1,79 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class PixelBlocksCommand implements CommandExecutor, TabCompleter {
private final List<SubCommand> subCommands = List.of(
new CreateSubCommand(),
new GiveSubCommand(),
new ExitSubCommand(),
new DestroyAllSubCommand()
);
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!(sender instanceof Player player)) {
sender.sendMessage(Component.text("Dieser Befehl kann nur von Spielern ausgeführt werden.", NamedTextColor.RED));
return true;
}
Optional<SubCommand> subCommand = args.length == 0
? Optional.empty()
: this.subCommands.stream().filter(sub -> sub.name().equalsIgnoreCase(args[0])).findFirst();
if(subCommand.isEmpty()) {
this.sendUsage(player);
return true;
}
if(!player.hasPermission(subCommand.get().permission())) {
player.sendMessage(Component.text("Dazu hast du keine Berechtigung.", NamedTextColor.RED));
return true;
}
subCommand.get().execute(player, Arrays.copyOfRange(args, 1, args.length));
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!(sender instanceof Player player)) return List.of();
if(args.length == 1) {
return this.subCommands.stream()
.filter(sub -> player.hasPermission(sub.permission()))
.map(SubCommand::name)
.filter(name -> name.startsWith(args[0].toLowerCase()))
.toList();
}
return this.subCommands.stream()
.filter(sub -> sub.name().equalsIgnoreCase(args[0]))
.filter(sub -> player.hasPermission(sub.permission()))
.findFirst()
.map(sub -> sub.tabComplete(player, Arrays.copyOfRange(args, 1, args.length)))
.orElse(List.of());
}
private void sendUsage(@NotNull Player player) {
player.sendMessage(Component.text("PixelBlocks-Befehle:", NamedTextColor.GOLD));
this.subCommands.stream()
.filter(sub -> player.hasPermission(sub.permission()))
.forEach(sub -> player.sendMessage(Component.text()
.append(Component.text("/pixelblocks " + sub.name(), NamedTextColor.YELLOW))
.append(Component.text(" - " + sub.description(), NamedTextColor.GRAY))
.build()));
}
}
@@ -0,0 +1,36 @@
package eu.mhsl.minecraft.pixelblocks.commands;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public abstract class SubCommand {
private final String name;
private final String permission;
private final String description;
protected SubCommand(@NotNull String name, @NotNull String permission, @NotNull String description) {
this.name = name;
this.permission = permission;
this.description = description;
}
public final @NotNull String name() {
return name;
}
public final @NotNull String permission() {
return permission;
}
public final @NotNull String description() {
return description;
}
public abstract void execute(@NotNull Player player, @NotNull String[] args);
public @NotNull List<String> tabComplete(@NotNull Player player, @NotNull String[] args) {
return List.of();
}
}
@@ -15,6 +15,6 @@ public class BreakPixelBlockListener implements Listener {
Location blockLocation = event.getAttacked().getLocation().toBlockLocation();
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromPlacedLocation(blockLocation);
if(pixelBlock == null) return;
pixelBlock.destroy(event.getPlayer());
pixelBlock.destroy(event.getPlayer(), false);
}
}
@@ -174,10 +174,10 @@ public class PixelBlock {
.execute();
}
public void destroy(Player destroyedBy) {
public void destroy(Player destroyedBy, boolean force) {
if(!this.isAccessible) return;
if(Main.configuration().onlyBreakableByOwner() && !destroyedBy.getUniqueId().equals(ownerUUID)) {
destroyedBy.sendMessage("Dieser Pixelblock gehört nicht dir!");
if(!force && Main.configuration().onlyBreakableByOwner() && !destroyedBy.getUniqueId().equals(ownerUUID)) {
destroyedBy.sendMessage(Component.text("Dieser Pixelblock gehört nicht dir!", NamedTextColor.RED));
return;
}
+17 -4
View File
@@ -3,7 +3,20 @@ version: '${version}'
main: eu.mhsl.minecraft.pixelblocks.Main
api-version: '1.21'
commands:
createpixelblock:
exitworld:
givepixelblock:
destroypixelblocks:
pixelblocks:
description: Verwaltung von Pixelblöcken
usage: /pixelblocks <create|give|exit|destroyall>
aliases: [pb]
permissions:
pixelblocks.command.create:
description: Erlaubt das Erstellen eines Pixelblocks per Befehl
default: op
pixelblocks.command.give:
description: Erlaubt das Geben von Pixelblock-Items
default: op
pixelblocks.command.exit:
description: Erlaubt das Verlassen eines Pixelblocks per Befehl
default: true
pixelblocks.command.destroyall:
description: Erlaubt das Zerstören aller Pixelblöcke
default: op