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.
This commit is contained in:
2026-07-23 22:59:00 +02:00
parent a024e42b80
commit 0aa098ae8f
9 changed files with 129 additions and 133 deletions
@@ -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);
}
@@ -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());
}
}
@@ -38,20 +38,14 @@ 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));
}
@@ -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;
}