Compare commits

..

4 Commits

Author SHA1 Message Date
MineTec 0aa098ae8f simplified code: shared entity tagging, world name scheme, less boilerplate
New EntityTagUtil replaces the three copies of the tag/find/remove
logic for display and interaction entities. The pixel world naming
scheme (build, parse, recognize) now lives in one place in
PixelBlockWorld with a cached base path instead of allocating on every
event. getPixels works with int arithmetic instead of two Location
allocations per cell, setBuildingPlatform lost its copy-pasted loops
and rebuilds the flower list only once, SubCommand is an abstract base
class instead of an interface with 12 trivial getters, and dead
methods were removed.
2026-07-23 22:59:00 +02:00
MineTec a024e42b80 fixed NPE and shutdown issues, cleaned up listeners and build config
Blocks destroyed before ever being entered no longer NPE on the null
entry location (new getReturnLocation fallback), destroy no longer
mutates the stored block location, placing an item whose block UUID
already exists cancels the event with a message instead of eating the
item, and pending task chains are flushed on shutdown. Failed block
initialization now propagates instead of leaving half-built blocks
registered. Listener null checks replaced requireNonNull, listener and
method name typos fixed, and the test-server copy path in build.gradle
is now a gradle property instead of a hardcoded home directory.
2026-07-23 22:58:47 +02:00
MineTec 76ddfc95af 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.
2026-07-23 22:57:48 +02:00
MineTec bc43519077 moved database access to a dedicated single-thread executor
SQLite I/O no longer runs on the main server thread and the shared
PreparedStatements are confined to one thread. savePixelBlock captures
a snapshot of the block data on the calling thread. Main.pixelBlocks is
now a CopyOnWriteArrayList and list mutations moved out of async chains.
Blocks that fail to initialize on startup are skipped with a proper
error log instead of being registered half-built.
2026-07-23 22:57:22 +02:00
32 changed files with 563 additions and 368 deletions
+4 -1
View File
@@ -45,8 +45,11 @@ processResources {
}
}
// Zielverzeichnis über testServerPluginsDir in gradle.properties oder -PtestServerPluginsDir=... setzen
tasks.register('copyJarToTestServer', Exec) {
commandLine 'cp', 'build/libs/PixelBlocks-1.0-SNAPSHOT-all.jar', '/home/lars/Documents/Minecraft/Server/pixelblocks/plugins/PixelBlocks-1.0-SNAPSHOT-all.jar'
def pluginsDir = providers.gradleProperty('testServerPluginsDir').getOrNull()
onlyIf { pluginsDir != null }
commandLine 'cp', "build/libs/PixelBlocks-${version}-all.jar", "${pluginsDir}/PixelBlocks-${version}-all.jar"
}
shadowJar {
@@ -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;
@@ -15,10 +13,10 @@ import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public final class Main extends JavaPlugin {
@@ -28,7 +26,7 @@ public final class Main extends JavaPlugin {
private static TaskChainFactory taskFactory;
public final static List<PixelBlock> pixelBlocks = new ArrayList<>();
public final static List<PixelBlock> pixelBlocks = new CopyOnWriteArrayList<>();
public static <T> TaskChain<T> sharedChain(String name) {
return taskFactory.newSharedChain(name);
@@ -64,7 +62,7 @@ public final class Main extends JavaPlugin {
new FallOutOfPixelBlockListener(),
new BreakPixelListener(),
new PlacePixelBlockListener(),
new PreventInventorysListener(),
new PreventInventoriesListener(),
new ExitPixelWorldListener(),
new PreventIllegalBlocksListener(),
new BreakPixelBlockListener(),
@@ -83,10 +81,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());
}
@@ -94,11 +92,8 @@ public final class Main extends JavaPlugin {
@Override
public void onDisable() {
Bukkit.getOnlinePlayers().forEach(QuitWhileInPixelBlockListener::kickPlayerOutOfWorld);
try {
database.close();
} catch(SQLException e) {
throw new RuntimeException("Failed disabling", e);
}
taskFactory.shutdown(5, TimeUnit.SECONDS);
database.close();
}
public static Main plugin() {
@@ -9,9 +9,9 @@ public record PixelBlockConfiguration(
boolean onlyEditableByOwner
) {
public static void setDefaults(FileConfiguration config) {
config.addDefault(Keys.PixelsPerBlock.key, 16);
config.addDefault(Keys.OnlyBreakableByOwners.key, false);
config.addDefault(Keys.OnlyEditableByOwners.key, true);
config.addDefault(Keys.PixelsPerBlock.getKey(), 16);
config.addDefault(Keys.OnlyBreakableByOwners.getKey(), false);
config.addDefault(Keys.OnlyEditableByOwners.getKey(), true);
config.options().copyDefaults(true);
}
@@ -7,14 +7,38 @@ import org.bukkit.Location;
import java.sql.*;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class PixelBlockDatabase {
private final Connection db;
// Alle Statement-Zugriffe nach dem Startup laufen ausschließlich auf diesem Thread,
// da die geteilten PreparedStatements nicht threadsicher sind.
private final ExecutorService executor =
Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "PixelBlocks-Database"));
private final PreparedStatement getAllPixelBlocks;
private final PreparedStatement deletePixelBlock;
private final PreparedStatement insertOrReplacePixelBlock;
private record PixelBlockRow(
String uuid,
String owner,
String worldName,
double x,
double y,
double z,
String entryWorldName,
double entryX,
double entryY,
double entryZ,
String direction
) {
}
public PixelBlockDatabase(String url) {
try {
Class.forName("org.sqlite.JDBC");
@@ -50,52 +74,78 @@ public class PixelBlockDatabase {
}
}
public void close() throws SQLException {
deletePixelBlock.close();
getAllPixelBlocks.close();
insertOrReplacePixelBlock.close();
db.close();
public void close() {
this.executor.shutdown();
try {
if(!this.executor.awaitTermination(10, TimeUnit.SECONDS)) {
Main.logger().warning("Database executor did not terminate in time, forcing shutdown");
this.executor.shutdownNow();
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
this.executor.shutdownNow();
}
try {
this.deletePixelBlock.close();
this.getAllPixelBlocks.close();
this.insertOrReplacePixelBlock.close();
this.db.close();
} catch(SQLException e) {
Main.logger().log(Level.SEVERE, "Failed closing the database", e);
}
}
public void deletePixelBlock(PixelBlock pixelBlock) {
Bukkit.getScheduler().runTaskAsynchronously(Main.plugin(), () -> {
String uuid = pixelBlock.getBlockUUID().toString();
this.executor.execute(() -> {
try {
this.deletePixelBlock.setString(1, pixelBlock.getBlockUUID().toString());
this.deletePixelBlock.setString(1, uuid);
this.deletePixelBlock.executeUpdate();
} catch(SQLException e) {
throw new RuntimeException("Failed to delete PixelBlock from the database", e);
Main.logger().log(Level.SEVERE, String.format("Failed to delete PixelBlock '%s' from the database", uuid), e);
}
});
}
public void savePixelBlock(PixelBlock pixelBlock) {
Bukkit.getScheduler().runTask(Main.plugin(), () -> {
Location blockLocation = pixelBlock.getPixelBlockLocation();
Location entryLocation = pixelBlock.hasLastEntryLocation() ? pixelBlock.getLastEntryLocation() : blockLocation;
PixelBlockRow row = new PixelBlockRow(
pixelBlock.getBlockUUID().toString(),
pixelBlock.getOwnerUUID().toString(),
blockLocation.getWorld().getName(),
blockLocation.getX(),
blockLocation.getY(),
blockLocation.getZ(),
entryLocation.getWorld().getName(),
entryLocation.getX(),
entryLocation.getY(),
entryLocation.getZ(),
pixelBlock.getFacingDirection().toString()
);
this.executor.execute(() -> {
try {
this.insertOrReplacePixelBlock.setString(1, pixelBlock.getBlockUUID().toString());
this.insertOrReplacePixelBlock.setString(2, pixelBlock.getOwnerUUID().toString());
this.insertOrReplacePixelBlock.setString(1, row.uuid());
this.insertOrReplacePixelBlock.setString(2, row.owner());
this.insertOrReplacePixelBlock.setString(3, pixelBlock.getPixelBlockLocation().getWorld().getName());
this.insertOrReplacePixelBlock.setDouble(4, pixelBlock.getPixelBlockLocation().getX());
this.insertOrReplacePixelBlock.setDouble(5, pixelBlock.getPixelBlockLocation().getY());
this.insertOrReplacePixelBlock.setDouble(6, pixelBlock.getPixelBlockLocation().getZ());
this.insertOrReplacePixelBlock.setString(3, row.worldName());
this.insertOrReplacePixelBlock.setDouble(4, row.x());
this.insertOrReplacePixelBlock.setDouble(5, row.y());
this.insertOrReplacePixelBlock.setDouble(6, row.z());
if(pixelBlock.hasLastEntryLocation()) {
this.insertOrReplacePixelBlock.setString(7, pixelBlock.getLastEntryLocation().getWorld().getName());
this.insertOrReplacePixelBlock.setDouble(8, pixelBlock.getLastEntryLocation().getX());
this.insertOrReplacePixelBlock.setDouble(9, pixelBlock.getLastEntryLocation().getY());
this.insertOrReplacePixelBlock.setDouble(10, pixelBlock.getLastEntryLocation().getZ());
} else {
this.insertOrReplacePixelBlock.setString(7, pixelBlock.getPixelBlockLocation().getWorld().getName());
this.insertOrReplacePixelBlock.setDouble(8, pixelBlock.getPixelBlockLocation().getX());
this.insertOrReplacePixelBlock.setDouble(9, pixelBlock.getPixelBlockLocation().getY());
this.insertOrReplacePixelBlock.setDouble(10, pixelBlock.getPixelBlockLocation().getZ());
}
this.insertOrReplacePixelBlock.setString(7, row.entryWorldName());
this.insertOrReplacePixelBlock.setDouble(8, row.entryX());
this.insertOrReplacePixelBlock.setDouble(9, row.entryY());
this.insertOrReplacePixelBlock.setDouble(10, row.entryZ());
this.insertOrReplacePixelBlock.setString(11, pixelBlock.getFacingDirection().toString());
this.insertOrReplacePixelBlock.setString(11, row.direction());
this.insertOrReplacePixelBlock.executeUpdate();
} catch(SQLException e) {
throw new RuntimeException("Failed to create or update PixelBlock in the database", e);
Main.logger().log(Level.SEVERE, String.format("Failed to create or update PixelBlock '%s' in the database", row.uuid()), e);
}
});
}
@@ -105,27 +155,32 @@ public class PixelBlockDatabase {
ResultSet allPixelBlocks = this.getAllPixelBlocks.executeQuery();
while(allPixelBlocks.next()) {
Location blockLocation = new Location(
Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")),
allPixelBlocks.getDouble("locationX"),
allPixelBlocks.getDouble("locationY"),
allPixelBlocks.getDouble("locationZ")
);
String uuid = allPixelBlocks.getString("uuid");
try {
Location blockLocation = new Location(
Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")),
allPixelBlocks.getDouble("locationX"),
allPixelBlocks.getDouble("locationY"),
allPixelBlocks.getDouble("locationZ")
);
Location entryLocation = new Location(
Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")),
allPixelBlocks.getDouble("entryLocationX"),
allPixelBlocks.getDouble("entryLocationY"),
allPixelBlocks.getDouble("entryLocationZ")
);
Location entryLocation = new Location(
Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")),
allPixelBlocks.getDouble("entryLocationX"),
allPixelBlocks.getDouble("entryLocationY"),
allPixelBlocks.getDouble("entryLocationZ")
);
Main.pixelBlocks.add(PixelBlock.fromExisting(
UUID.fromString(allPixelBlocks.getString("uuid")),
UUID.fromString(allPixelBlocks.getString("owner")),
blockLocation,
Direction.valueOf(allPixelBlocks.getString("direction")),
entryLocation
));
Main.pixelBlocks.add(PixelBlock.fromExisting(
UUID.fromString(uuid),
UUID.fromString(allPixelBlocks.getString("owner")),
blockLocation,
Direction.valueOf(allPixelBlocks.getString("direction")),
entryLocation
));
} catch(Exception e) {
Main.logger().log(Level.SEVERE, String.format("Failed initializing existing pixelblock '%s'", uuid), e);
}
}
} catch(SQLException e) {
throw new RuntimeException("Failed loading PixelBlocks from the database", e);
@@ -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);
}
}
@@ -5,11 +5,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.Objects;
import java.util.UUID;
public class CraftPixelBlockListener implements Listener {
@@ -17,14 +13,10 @@ public class CraftPixelBlockListener implements Listener {
public void onCraft(CraftItemEvent event) {
ItemStack result = event.getInventory().getResult();
if(result == null) return;
ItemMeta itemMeta = result.getItemMeta();
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
if(!dataContainer.has(PixelBlockItem.idProperty)) return;
String currentId = dataContainer.get(PixelBlockItem.idProperty, PersistentDataType.STRING);
Objects.requireNonNull(currentId);
if(!UUID.fromString(currentId).equals(PixelBlockItem.emptyBlockUUID)) return;
dataContainer.set(PixelBlockItem.idProperty, PersistentDataType.STRING, UUID.randomUUID().toString());
result.setItemMeta(itemMeta);
PixelBlockItem.BlockInfo info = PixelBlockItem.getBlockInfo(result);
if(info == null || !info.id().equals(PixelBlockItem.emptyBlockUUID)) return;
PixelBlockItem.setBlockId(result, UUID.randomUUID());
}
}
@@ -20,7 +20,7 @@ public class DiscoverRecipesListener implements Listener {
if(!(event.getWhoClicked() instanceof Player player)) return;
if(!List.of(Material.HEART_OF_THE_SEA, Material.END_CRYSTAL).contains(clickedItem.getType())) return;
if(player.hasDiscoveredRecipe(PixelBlockItem.recipeKey)) return;
Main.logger().log(Level.INFO, String.format("%s unlocked tne PixelBlock recipe!", player.getName()));
Main.logger().log(Level.INFO, String.format("%s unlocked the PixelBlock recipe!", player.getName()));
player.discoverRecipe(PixelBlockItem.recipeKey);
}
}
@@ -1,5 +1,6 @@
package eu.mhsl.minecraft.pixelblocks.listeners;
import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlockWorld;
import org.bukkit.World;
@@ -8,7 +9,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import java.util.Objects;
public class ExitPixelWorldListener implements Listener {
@EventHandler
@@ -18,7 +18,10 @@ public class ExitPixelWorldListener implements Listener {
event.setCancelled(true);
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(pixelBlockWorld);
Objects.requireNonNull(pixelBlock);
if(pixelBlock == null) {
Main.logger().warning("Player used a portal in an unknown pixel world: " + pixelBlockWorld.getName());
return;
}
pixelBlock.exitBlock(event.getPlayer());
}
@@ -7,7 +7,6 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import java.util.Objects;
public class FallOutOfPixelBlockListener implements Listener {
@EventHandler
@@ -17,7 +16,7 @@ public class FallOutOfPixelBlockListener implements Listener {
if(!PixelBlockWorld.isPixelWorld(player.getWorld())) return;
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(player.getWorld());
Objects.requireNonNull(pixelBlock);
if(pixelBlock == null) return;
player.teleport(pixelBlock.getPixelWorld().getSpawnLocation());
}
}
@@ -29,6 +29,12 @@ public class PlacePixelBlockListener implements Listener {
return;
}
if(PixelBlock.exists(info.id())) {
event.getPlayer().sendMessage(Component.text("Dieser Pixelblock existiert bereits in der Welt!", NamedTextColor.RED));
event.setCancelled(true);
return;
}
Location newBlockLocation = event.getBlock().getLocation();
playerWorld.getBlockAt(newBlockLocation).setType(Material.AIR);
@@ -17,7 +17,7 @@ public class PlacePixelListener implements Listener {
}
@EventHandler
public void onBuketEmpty(PlayerBucketEmptyEvent event) {
public void onBucketEmpty(PlayerBucketEmptyEvent event) {
EventCanceling.shouldCancelInPixelBlock(
event,
event.getBlock().getWorld(),
@@ -10,7 +10,7 @@ import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.PlayerInventory;
public class PreventInventorysListener implements Listener {
public class PreventInventoriesListener implements Listener {
@EventHandler
public void onInventoryOpen(InventoryOpenEvent event) {
EventCanceling.shouldCancelInPixelBlock(
@@ -8,7 +8,6 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Objects;
public class QuitWhileInPixelBlockListener implements Listener {
@EventHandler
@@ -20,7 +19,7 @@ public class QuitWhileInPixelBlockListener implements Listener {
World pixelBlockWorld = player.getLocation().getWorld();
if(!PixelBlockWorld.isPixelWorld(pixelBlockWorld)) return;
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(pixelBlockWorld);
Objects.requireNonNull(pixelBlock);
if(pixelBlock == null) return;
pixelBlock.exitBlock(player);
}
}
@@ -38,18 +38,16 @@ public class PixelBlock {
private final UUID blockUUID;
public static @Nullable PixelBlock getPixelBlockFromBlockWorld(World world) {
UUID worldUUID = PixelBlockWorld.getUUIDFromWorld(world);
if(worldUUID == null) return null;
return Main.pixelBlocks.stream()
.filter(block -> block.blockUUID.equals(getUUIDFromWorld(world)))
.filter(block -> block.blockUUID.equals(worldUUID))
.findFirst()
.orElse(null);
}
public static @Nullable UUID getUUIDFromWorld(@NotNull World world) {
try {
return UUID.fromString(List.of(world.getName().split("/")).getLast());
} catch(IllegalArgumentException e) {
return null;
}
public static boolean exists(@NotNull UUID blockUUID) {
return Main.pixelBlocks.stream().anyMatch(pixelBlock -> pixelBlock.blockUUID.equals(blockUUID));
}
public static @Nullable PixelBlock getPixelBlockFromPlacedLocation(@NotNull Location placedLocation) {
@@ -76,19 +74,15 @@ public class PixelBlock {
this.facingDirection = direction;
this.lastEntryLocation = lastEntryLocation;
try {
this.pixelWorld = new PixelBlockWorld(this);
this.pixelData = this.pixelWorld.getPixels(this.facingDirection);
this.pixels = new Pixels(this);
this.placeholder = new PixelBlockPlaceholder(this);
this.hitbox = new PixelBlockHitbox(this);
this.pixelWorld = new PixelBlockWorld(this);
this.pixelData = this.pixelWorld.getPixels(this.facingDirection);
this.pixels = new Pixels(this);
this.placeholder = new PixelBlockPlaceholder(this);
this.hitbox = new PixelBlockHitbox(this);
this.getBlockTaskChain().sync(() -> this.isAccessible = true).execute();
this.getBlockTaskChain().sync(() -> this.isAccessible = true).execute();
Main.logger().info(String.format("Loaded existing pixelblock '%s'", this.blockUUID));
} catch(Exception e) {
Main.logger().info(String.format("Failed initializing existing pixelblock '%s': %s", this.blockUUID, e.getMessage()));
}
Main.logger().info(String.format("Loaded existing pixelblock '%s'", this.blockUUID));
}
public static PixelBlock createPixelBlock(UUID blockUUID, UUID ownerUUID, Location pixelBlockLocation, Direction direction) {
@@ -96,8 +90,8 @@ public class PixelBlock {
}
private PixelBlock(UUID blockUUID, UUID ownerUUID, Location pixelBlockLocation, Direction direction) {
if(Main.pixelBlocks.stream().anyMatch(pixelBlock -> pixelBlock.getBlockUUID().equals(blockUUID)))
throw new IllegalStateException(String.format("PixelBlock '%s' ist bereits in der Welt vorhanden!", blockUUID));
if(exists(blockUUID))
throw new IllegalStateException(String.format("PixelBlock '%s' already exists in the world!", blockUUID));
this.blockUUID = blockUUID;
this.ownerUUID = ownerUUID;
@@ -118,12 +112,8 @@ public class PixelBlock {
this.scheduleEntityUpdate();
this.getBlockTaskChain()
.async(() -> {
Main.database().savePixelBlock(this);
Main.pixelBlocks.add(this);
})
.execute();
Main.database().savePixelBlock(this);
Main.pixelBlocks.add(this);
this.getBlockTaskChain().sync(() -> this.isAccessible = true).execute();
}
@@ -139,9 +129,9 @@ public class PixelBlock {
}
this.lastEntryLocation = player.getLocation();
Main.database().savePixelBlock(this);
getBlockTaskChain()
.async(() -> Main.database().savePixelBlock(this))
.sync(() -> {
if(!this.isAccessible) return;
player.teleport(this.pixelWorld.getSpawnLocation());
@@ -153,7 +143,7 @@ public class PixelBlock {
public void exitBlock(@NotNull Player player) {
this.getBlockTaskChain()
.sync(() -> player.teleport(this.lastEntryLocation != null ? this.lastEntryLocation : this.pixelBlockLocation))
.sync(() -> player.teleport(this.getReturnLocation()))
.sync(() -> this.pixelData = this.pixelWorld.getPixels(this.facingDirection))
.current(() -> Main.logger().info(String.format("%s exited PixelBlock", player.getName())))
.delay(1)
@@ -178,16 +168,17 @@ 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;
}
Location returnLocation = this.getReturnLocation();
this.pixelWorld.getPlayersInWorld().forEach(p -> {
p.sendMessage(Component.text("Der Pixelblock wurde von einem anderen Spieler abgebaut!", NamedTextColor.RED));
p.teleport(this.lastEntryLocation);
p.teleport(returnLocation);
});
Main.logger().info(String.format("Destroying PixelBlock '%s' at %s", this.blockUUID, pixelBlockLocation));
@@ -195,20 +186,19 @@ public class PixelBlock {
this.pixelWorld.getEntitiesInWorld().stream()
.filter(entity -> entity instanceof Item)
.forEach(entity -> entity.teleport(this.lastEntryLocation));
.forEach(entity -> entity.teleport(returnLocation));
this.getBlockTaskChain()
.sync(() -> {
this.removeEntities();
World world = this.pixelBlockLocation.getWorld();
world.playSound(this.pixelBlockLocation, Sound.BLOCK_COPPER_BULB_BREAK, 1.0F, 30);
world.dropItem(this.pixelBlockLocation.add(new Vector(0.5, 0.5, 0.5)), PixelBlockItem.getBlockAsItem(this));
})
.async(() -> {
Main.database().deletePixelBlock(this);
Main.pixelBlocks.remove(this);
world.playSound(this.pixelBlockLocation, Sound.BLOCK_COPPER_BULB_BREAK, 1.0F, 2.0F);
world.dropItem(this.getPixelBlockLocation().add(new Vector(0.5, 0.5, 0.5)), PixelBlockItem.getBlockAsItem(this));
})
.execute();
Main.database().deletePixelBlock(this);
Main.pixelBlocks.remove(this);
}
private void removeEntities() {
@@ -243,6 +233,12 @@ public class PixelBlock {
return this.lastEntryLocation != null;
}
private @NotNull Location getReturnLocation() {
return this.hasLastEntryLocation()
? this.lastEntryLocation.clone()
: this.getPixelBlockLocation().add(0.5, 0, 0.5);
}
public List<PixelBlockWorld.PixelData> getPixelData() {
return pixelData;
}
@@ -1,16 +1,14 @@
package eu.mhsl.minecraft.pixelblocks.pixelblock;
import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.utils.EntityTagUtil;
import eu.mhsl.minecraft.pixelblocks.utils.MinMaxUtil;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Interaction;
import org.bukkit.persistence.PersistentDataType;
import java.util.List;
import java.util.Objects;
public class PixelBlockHitbox {
private static final NamespacedKey hitboxOfTag = new NamespacedKey(Main.plugin(), "hitbox_of");
@@ -29,7 +27,7 @@ public class PixelBlockHitbox {
Interaction interaction;
if (pixels.size() <= 5) {
interaction = (Interaction) absoluteLocation.getWorld().spawnEntity(
absoluteLocation.clone().add(0.5, -0, 0.5),
absoluteLocation.clone().add(0.5, 0, 0.5),
EntityType.INTERACTION
);
interaction.setInteractionHeight(1);
@@ -45,7 +43,7 @@ public class PixelBlockHitbox {
Location spawnLocation = absoluteLocation.clone().add(
((startingX+endingX)/2+0.5)/pixelsPerBlock,
(startingY/pixelsPerBlock)-0,
startingY/pixelsPerBlock,
((startingZ+endingZ)/2+0.5)/pixelsPerBlock
);
@@ -81,19 +79,10 @@ public class PixelBlockHitbox {
interaction.setInteractionWidth(width);
}
interaction.getPersistentDataContainer()
.set(hitboxOfTag, PersistentDataType.STRING, this.parentBlock.getBlockUUID().toString());
EntityTagUtil.tag(interaction, hitboxOfTag, this.parentBlock.getBlockUUID());
}
public void destroy() {
this.parentBlock.getPixelBlockLocation().getNearbyEntitiesByType(Interaction.class, 1)
.stream()
.filter(interaction -> interaction.getPersistentDataContainer().has(hitboxOfTag))
.filter(interaction -> Objects.equals(
interaction.getPersistentDataContainer().get(hitboxOfTag, PersistentDataType.STRING),
parentBlock.getBlockUUID().toString()
))
.forEach(Entity::remove);
EntityTagUtil.removeTagged(this.parentBlock.getPixelBlockLocation(), Interaction.class, hitboxOfTag, this.parentBlock.getBlockUUID());
}
}
@@ -1,21 +1,18 @@
package eu.mhsl.minecraft.pixelblocks.pixelblock;
import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.utils.EntityTagUtil;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Transformation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class PixelBlockPlaceholder {
@@ -67,20 +64,10 @@ public class PixelBlockPlaceholder {
displayContainer.setItemStack(ItemStack.of(Material.WHITE_STAINED_GLASS));
placeholders.add(displayContainer);
placeholders.stream()
.map(PersistentDataHolder::getPersistentDataContainer)
.forEach(container -> container.set(placeholderOfTag, PersistentDataType.STRING, parentBlockUUID.toString()));
placeholders.forEach(placeholder -> EntityTagUtil.tag(placeholder, placeholderOfTag, parentBlockUUID));
}
public void destroy() {
this.parentBlock.getPixelBlockLocation()
.getNearbyEntitiesByType(ItemDisplay.class, 1)
.stream()
.filter(itemDisplay -> itemDisplay.getPersistentDataContainer().has(placeholderOfTag))
.filter(itemDisplay -> Objects.equals(
itemDisplay.getPersistentDataContainer().get(placeholderOfTag, PersistentDataType.STRING),
parentBlock.getBlockUUID().toString()
))
.forEach(Entity::remove);
EntityTagUtil.removeTagged(this.parentBlock.getPixelBlockLocation(), ItemDisplay.class, placeholderOfTag, this.parentBlock.getBlockUUID());
}
}
@@ -4,6 +4,7 @@ import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import eu.mhsl.minecraft.pixelblocks.utils.LocationUtil;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
@@ -20,6 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
public class PixelBlockWorld {
private final PixelBlock parentPixelBlock;
@@ -28,16 +30,28 @@ public class PixelBlockWorld {
int worldGrassBorderWidth = 10;
int pixelsPerBlock = Main.configuration().pixelsPerBlock();
// Kapselt das Namensschema der Pixelwelten: <dataFolder>/worlds/<blockUUID>
private static String worldsBasePath;
private static @NotNull String getWorldsBasePath() {
if(worldsBasePath == null) {
worldsBasePath = Main.plugin().getDataFolder().getPath() + File.separator + "worlds";
}
return worldsBasePath;
}
public static boolean isPixelWorld(@NotNull World world) {
return world.getName().startsWith(Main.plugin().getDataFolder().getPath());
return world.getName().startsWith(getWorldsBasePath());
}
public static @NotNull List<World> getOtherWorlds() {
return Bukkit.getWorlds().stream().filter(w -> !PixelBlockWorld.isPixelWorld(w)).toList();
}
public static @NotNull List<World> getPixelBlockWorlds() {
return Bukkit.getWorlds().stream().filter(PixelBlockWorld::isPixelWorld).toList();
public static @Nullable UUID getUUIDFromWorld(@NotNull World world) {
if(!isPixelWorld(world)) return null;
String worldName = world.getName();
try {
return UUID.fromString(worldName.substring(worldName.lastIndexOf(File.separatorChar) + 1));
} catch(IllegalArgumentException e) {
return null;
}
}
public PixelBlockWorld(PixelBlock parentPixelBlock) {
@@ -55,7 +69,7 @@ public class PixelBlockWorld {
}
public @NotNull String getWorldPathName() {
return Main.plugin().getDataFolder().getPath() + File.separator + "worlds" + File.separator + this.parentPixelBlock.getBlockUUID();
return getWorldsBasePath() + File.separator + this.parentPixelBlock.getBlockUUID();
}
public @NotNull Location getSpawnLocation() {
@@ -78,10 +92,6 @@ public class PixelBlockWorld {
return new Location(this.world, 0, -60, 0);
}
public @NotNull Location getBuildOriginEnd() {
return getBuildOrigin().add(pixelsPerBlock, pixelsPerBlock, pixelsPerBlock);
}
public @NotNull Location getBorderOrigin() {
return getBuildOrigin().subtract(1, 1, 1);
}
@@ -99,31 +109,32 @@ public class PixelBlockWorld {
public List<PixelData> getPixels(Direction direction) {
List<PixelData> pixelData = new ArrayList<>();
Location origin = this.getBuildOrigin();
int max = pixelsPerBlock - 1;
for(int x = 0; x < pixelsPerBlock; x++) {
for(int y = 0; y < pixelsPerBlock; y++) {
for(int z = 0; z < pixelsPerBlock; z++) {
Location relativeLocation = new Location(world, x, y, z);
int blockX = switch(direction) {
case south -> x;
case north -> max - x;
case east -> max - z;
case west -> z;
};
int blockZ = switch(direction) {
case south -> z;
case north -> max - z;
case east -> x;
case west -> max - x;
};
Block block = this.world.getBlockAt(origin.getBlockX() + blockX, origin.getBlockY() + y, origin.getBlockZ() + blockZ);
BlockData blockData = block.getBlockData();
if(blockData.getMaterial().isAir()) continue;
Location blockLocation = this.getBuildOrigin();
switch(direction) {
case south ->
blockLocation.add(relativeLocation.x(), relativeLocation.y(), relativeLocation.z());
case north ->
blockLocation.add((pixelsPerBlock - 1) - relativeLocation.x(), relativeLocation.y(), (pixelsPerBlock - 1) - relativeLocation.z());
case east ->
blockLocation.add((pixelsPerBlock - 1) - relativeLocation.z(), relativeLocation.y(), relativeLocation.x());
case west ->
blockLocation.add(relativeLocation.z(), relativeLocation.y(), (pixelsPerBlock - 1) - relativeLocation.x());
}
BlockData blockData = blockLocation.getBlock().getBlockData();
@Nullable Directional directional = blockData instanceof Directional face ? face : null;
@Nullable Rotatable rotatable = blockData instanceof Rotatable rotation ? rotation : null;
BlockState state = blockLocation.getBlock().getState();
if(!blockData.getMaterial().isAir()) {
pixelData.add(new PixelData(relativeLocation.toVector(), blockData, directional, rotatable, state, (double) 1 / pixelsPerBlock));
}
pixelData.add(new PixelData(new Vector(x, y, z), blockData, directional, rotatable, block.getState(), (double) 1 / pixelsPerBlock));
}
}
}
@@ -156,27 +167,38 @@ public class PixelBlockWorld {
return world;
}
private static final List<Material> FLOWERS = List.of(
Material.DANDELION,
Material.POPPY,
Material.BLUE_ORCHID,
Material.ALLIUM,
Material.AZURE_BLUET,
Material.RED_TULIP,
Material.ORANGE_TULIP,
Material.WHITE_TULIP,
Material.CORNFLOWER,
Material.LILY_OF_THE_VALLEY,
Material.SHORT_GRASS,
Material.TALL_GRASS
);
private void setBuildingPlatform() {
Bukkit.getScheduler().runTask(Main.plugin(), () -> {
for(int x = 0; x < (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth; x++) {
for(int z = 0; z < (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth; z++) {
getPlatformOrigin().add(x, 0, z).getBlock().setType(Material.GRASS_BLOCK);
}
}
for(int x = 0; x < (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth; x++) {
for(int z = 0; z < (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth; z++) {
getPlatformOrigin().add(x, -1, z).getBlock().setType(Material.DIRT);
int platformSize = (pixelsPerBlock + 2) + 2 * worldGrassBorderWidth;
Location platformOrigin = this.getPlatformOrigin();
for(int x = 0; x < platformSize; x++) {
for(int z = 0; z < platformSize; z++) {
platformOrigin.clone().add(x, 0, z).getBlock().setType(Material.GRASS_BLOCK);
platformOrigin.clone().add(x, -1, z).getBlock().setType(Material.DIRT);
}
}
for(int x = 0; x < (pixelsPerBlock + 2); x++) {
for(int z = 0; z < (pixelsPerBlock + 2); z++) {
Location currentLocation = getBorderOrigin().add(x, 0, z);
if(currentLocation.x() == getBorderOrigin().x() || currentLocation.z() == getBorderOrigin().z()) {
currentLocation.getBlock().setType(Material.RED_CONCRETE);
} else if(currentLocation.x() == getBorderOrigin().x() + (pixelsPerBlock + 1) || currentLocation.z() == getBorderOrigin().z() + (pixelsPerBlock + 1)) {
currentLocation.getBlock().setType(Material.RED_CONCRETE);
int borderSize = pixelsPerBlock + 2;
Location borderOrigin = this.getBorderOrigin();
for(int x = 0; x < borderSize; x++) {
for(int z = 0; z < borderSize; z++) {
if(x == 0 || z == 0 || x == borderSize - 1 || z == borderSize - 1) {
borderOrigin.clone().add(x, 0, z).getBlock().setType(Material.RED_CONCRETE);
}
}
}
@@ -186,37 +208,23 @@ public class PixelBlockWorld {
if(allowPlacements(location)) return;
if(!location.clone().subtract(0, 1, 0).getBlock().getType().equals(Material.GRASS_BLOCK)) return;
List<Material> flowers = List.of(
Material.DANDELION,
Material.POPPY,
Material.BLUE_ORCHID,
Material.ALLIUM,
Material.AZURE_BLUET,
Material.RED_TULIP,
Material.ORANGE_TULIP,
Material.WHITE_TULIP,
Material.CORNFLOWER,
Material.LILY_OF_THE_VALLEY,
Material.SHORT_GRASS,
Material.TALL_GRASS
);
if(flowers.contains(location.getBlock().getType())) location.getBlock().setType(Material.AIR);
if(FLOWERS.contains(location.getBlock().getType())) location.getBlock().setType(Material.AIR);
if(!location.getBlock().getType().equals(Material.AIR)) return;
if(random.nextInt(30) == 0) {
Material randomFlower = flowers.get(random.nextInt(flowers.size()));
location.getBlock().setType(randomFlower);
location.getBlock().setType(FLOWERS.get(random.nextInt(FLOWERS.size())));
}
});
Location portalLocation = this.getPortalLocation();
for(int x = 0; x < 4; x++) {
for(int y = 0; y < 5; y++) {
getPortalLocation().add(x, y, 0).getBlock().setType(Material.OBSIDIAN);
portalLocation.clone().add(x, y, 0).getBlock().setType(Material.OBSIDIAN);
}
}
for(int x = 1; x < 3; x++) {
for(int y = 1; y < 4; y++) {
getPortalLocation().add(x, y, 0).getBlock().setType(Material.NETHER_PORTAL);
portalLocation.clone().add(x, y, 0).getBlock().setType(Material.NETHER_PORTAL);
}
}
});
@@ -2,6 +2,7 @@ package eu.mhsl.minecraft.pixelblocks.pixelblock;
import eu.mhsl.minecraft.pixelblocks.Main;
import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import eu.mhsl.minecraft.pixelblocks.utils.EntityTagUtil;
import eu.mhsl.minecraft.pixelblocks.utils.ListUtil;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
@@ -16,7 +17,6 @@ import org.bukkit.block.data.type.EnderChest;
import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Transformation;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable;
import org.joml.Vector3d;
import java.util.List;
import java.util.Objects;
public class Pixels {
private static final NamespacedKey pixelOfTag = new NamespacedKey(Main.plugin(), "pixel_of");
@@ -61,18 +60,16 @@ public class Pixels {
transform.getTranslation().set(centerOffset.mul(-1));
entity.setTransformation(transform);
entity.getPersistentDataContainer().set(pixelOfTag, PersistentDataType.STRING, this.parentBlock.getBlockUUID().toString());
EntityTagUtil.tag(entity, pixelOfTag, this.parentBlock.getBlockUUID());
}
public void destroy() {
List<BlockDisplay> entities = parentBlock.getPixelBlockLocation().getNearbyEntitiesByType(BlockDisplay.class, 1)
.stream()
.filter(blockDisplay -> blockDisplay.getPersistentDataContainer().has(pixelOfTag))
.filter(blockDisplay -> Objects.equals(
blockDisplay.getPersistentDataContainer().get(pixelOfTag, PersistentDataType.STRING),
parentBlock.getBlockUUID().toString()
))
.toList();
List<BlockDisplay> entities = EntityTagUtil.findTagged(
this.parentBlock.getPixelBlockLocation(),
BlockDisplay.class,
pixelOfTag,
this.parentBlock.getBlockUUID()
);
ListUtil.splitListInParts(10, entities)
.forEach(pixels -> parentBlock.getBlockTaskChain()
@@ -0,0 +1,29 @@
package eu.mhsl.minecraft.pixelblocks.utils;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class EntityTagUtil {
public static void tag(@NotNull Entity entity, @NotNull NamespacedKey key, @NotNull UUID id) {
entity.getPersistentDataContainer().set(key, PersistentDataType.STRING, id.toString());
}
public static <T extends Entity> @NotNull List<T> findTagged(@NotNull Location center, @NotNull Class<T> type, @NotNull NamespacedKey key, @NotNull UUID id) {
String idString = id.toString();
return center.getNearbyEntitiesByType(type, 1)
.stream()
.filter(entity -> Objects.equals(entity.getPersistentDataContainer().get(key, PersistentDataType.STRING), idString))
.toList();
}
public static <T extends Entity> void removeTagged(@NotNull Location center, @NotNull Class<T> type, @NotNull NamespacedKey key, @NotNull UUID id) {
findTagged(center, type, key, id).forEach(Entity::remove);
}
}
@@ -14,7 +14,7 @@ public class EventCanceling {
if(!PixelBlockWorld.isPixelWorld(world)) return;
@Nullable PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(world);
if(pixelBlock == null) {
Main.logger().warning("Cancelling place event because PixelBlock could not be found: " + world.getName());
Main.logger().warning("Cancelling %s event because PixelBlock '%s' could not be found!".formatted(event.getClass().getSimpleName(), world.getName()));
event.setCancelled(true);
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