diff --git a/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java b/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java index d428372..1234942 100644 --- a/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java +++ b/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java @@ -1,6 +1,7 @@ package me.youhavetrouble.blockedit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; @@ -12,14 +13,30 @@ public class BEPlayer { private static final HashMap playerHashMap = new HashMap<>(); private BoundingBox selection; private Location selectionPoint1, selectionPoint2; + private World selectionWorld; public BoundingBox getSelection() { return selection; } private void updateSelection() { - if (selectionPoint1 == null || selectionPoint2 == null) return; + if (selectionPoint1 == null || selectionPoint2 == null) { + selection = null; + return; + } + if (selectionPoint1.getWorld() == null || selectionPoint2 == null) { + selection = null; + return; + } + if (!selectionPoint1.getWorld().equals(selectionPoint2.getWorld())) { + selection = null; + return; + } + selectionWorld = selectionPoint1.getWorld(); selection = BoundingBox.of(selectionPoint1, selectionPoint2); + // bounding boxes are dumb. + selection.expand(0.5, 0.5, 0.5); + selection.shift(0.5,0.5,0.5); } public void setSelectionPoint1(Location selectionPoint1) { @@ -34,6 +51,13 @@ public class BEPlayer { updateSelection(); } + /** + * @return World withinn which the selection is made. + */ + public World getSelectionWorld() { + return selectionWorld; + } + /** * @return Clone of selectionPoint1 */ diff --git a/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java index e134585..c6eda32 100644 --- a/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java +++ b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java @@ -1,8 +1,10 @@ package me.youhavetrouble.blockedit; import me.youhavetrouble.blockedit.api.BlockEditWands; +import me.youhavetrouble.blockedit.commands.SetCommand; import me.youhavetrouble.blockedit.commands.WandCommand; import me.youhavetrouble.blockedit.wands.SelectionWand; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; public final class BlockEdit extends JavaPlugin { @@ -19,11 +21,20 @@ public final class BlockEdit extends JavaPlugin { BlockEditWands.registerWand(selectionWand); getServer().getPluginManager().registerEvents(selectionWand, this); - getCommand("test").setExecutor(new TestCommand()); + //getCommand("test").setExecutor(new TestCommand()); WandCommand wandCommand = new WandCommand(); - getCommand("/wand").setExecutor(wandCommand); - getCommand("/wand").setTabCompleter(wandCommand); + PluginCommand bukkitWandCommand = getCommand("/wand"); + if (bukkitWandCommand != null) { + bukkitWandCommand.setExecutor(wandCommand); + bukkitWandCommand.setTabCompleter(wandCommand); + } + SetCommand setCommand = new SetCommand(); + PluginCommand bukkitSetCommand = getCommand("/set"); + if (bukkitSetCommand != null) { + bukkitSetCommand.setExecutor(setCommand); + bukkitSetCommand.setTabCompleter(setCommand); + } } diff --git a/src/main/java/me/youhavetrouble/blockedit/TestCommand.java b/src/main/java/me/youhavetrouble/blockedit/TestCommand.java index 3a31e33..ef5cff2 100644 --- a/src/main/java/me/youhavetrouble/blockedit/TestCommand.java +++ b/src/main/java/me/youhavetrouble/blockedit/TestCommand.java @@ -21,12 +21,13 @@ public class TestCommand implements CommandExecutor { if (sender instanceof Player player) { Location location = player.getLocation(); - ChunkWork work = WorkSplitter.locationToChunkWork(location.getX(), location.getZ(), location.getWorld()); if (task != null) { task.cancel(); } + // That's how manually checking if selection is valid looks like. task = Bukkit.getScheduler().runTaskTimerAsynchronously(BlockEdit.getPlugin(),() -> { - BoundingBox box = work.getWorkspace(null); + BoundingBox box = BEPlayer.getByPlayer(player).getSelection(); + if (box == null) return; for (int y = 0; y< 255; y++) { location.getWorld().spawnParticle(Particle.END_ROD, box.getMaxX(), y, box.getMaxZ(), 0, 0.01, 0.01, 0.01); location.getWorld().spawnParticle(Particle.END_ROD, box.getMinX(), y, box.getMinZ(), 0, 0.01, 0.01, 0.01); diff --git a/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java b/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java index 0f10fa7..6a0fdcc 100644 --- a/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java +++ b/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java @@ -5,26 +5,27 @@ import org.bukkit.World; import org.bukkit.util.BoundingBox; import java.util.HashSet; -import java.util.Set; public class WorkSplitter { - public static Set getOperatedOnChunks(BoundingBox boundingBox, World world) { + public static HashSet getOperatedOnChunks(BoundingBox boundingBox, World world) { HashSet chunks = new HashSet<>(); - for (double x = boundingBox.getMinX(); x<= boundingBox.getMaxX(); x+=16) { - for (double z = boundingBox.getMinZ(); z <= boundingBox.getMaxZ(); z+=16) { - ChunkWork chunkWork = locationToChunkWork(x,z, world); + ChunkWork chunkWork = new ChunkWork(0,0); + // TODO Find a way to get chunks in the selection more efficiently + for (double x = boundingBox.getMinX(); x <= boundingBox.getMaxX(); x++) { + for (double z = boundingBox.getMinZ(); z <= boundingBox.getMaxZ(); z++) { + chunkWork.setCoords(x,z); if (chunks.contains(chunkWork)) continue; - chunks.add(chunkWork); + chunks.add(chunkWork.clone()); } } return chunks; } - public static ChunkWork locationToChunkWork(double x, double z, World world) { + public static ChunkWork locationToChunkWork(double x, double z) { int chunkX = (int) x >> 4; int chunkZ = (int) z >> 4; - return new ChunkWork(chunkX, chunkZ, world); + return new ChunkWork(chunkX, chunkZ); } } diff --git a/src/main/java/me/youhavetrouble/blockedit/commands/SetCommand.java b/src/main/java/me/youhavetrouble/blockedit/commands/SetCommand.java new file mode 100644 index 0000000..c64ae82 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/commands/SetCommand.java @@ -0,0 +1,60 @@ +package me.youhavetrouble.blockedit.commands; + +import me.youhavetrouble.blockedit.BEPlayer; +import me.youhavetrouble.blockedit.WorkSplitter; +import me.youhavetrouble.blockedit.operations.SetOperation; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class SetCommand implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player player)) return true; + if (args.length == 0) { + player.sendMessage(Component.text("You need to provide block type")); + return true; + } else { + Material material = Material.getMaterial(args[0].toUpperCase()); + if (material == null) { + player.sendMessage(Component.text("Provided material does not exist")); + return true; + } + BlockData blockData = material.createBlockData(); + BEPlayer bePlayer = BEPlayer.getByPlayer(player); + BoundingBox selection = bePlayer.getSelection(); + if (selection == null) { + player.sendMessage(Component.text("You need to select 2 points to do this")); + return true; + } + new SetOperation(WorkSplitter.getOperatedOnChunks(selection, bePlayer.getSelectionWorld()), bePlayer.getSelectionWorld(), selection, blockData); + } + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (args.length == 1) { + ArrayList suggestions = new ArrayList<>(); + for (Material material : Material.values()) { + if (material.isBlock()) + suggestions.add(material.name().toLowerCase()); + } + return StringUtil.copyPartialMatches(args[0], suggestions, new ArrayList<>()); + } + return null; + } + + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/operations/SetOperation.java b/src/main/java/me/youhavetrouble/blockedit/operations/SetOperation.java new file mode 100644 index 0000000..70ef3a4 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/operations/SetOperation.java @@ -0,0 +1,42 @@ +package me.youhavetrouble.blockedit.operations; + +import me.youhavetrouble.blockedit.BlockEdit; +import me.youhavetrouble.blockedit.util.ChunkWork; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.util.BoundingBox; + +import java.util.HashSet; + +public class SetOperation { + + private final BoundingBox selection; + private final BlockData blockToSet; + + public SetOperation(HashSet chunkWorks, World world, BoundingBox selection, BlockData blockToSet) { + this.selection = selection; + this.blockToSet = blockToSet; + int stagger = 0; + for (ChunkWork chunkWork : chunkWorks) { + Bukkit.getScheduler().runTaskLater(BlockEdit.getPlugin(), () -> processChunkWork(chunkWork, world), stagger++); + } + } + + private void processChunkWork(ChunkWork chunkWork, World world) { + chunkWork.getChunkAsync(world).thenAccept(chunk -> { + // skip y levels that are not in the selection + for (int y = (int) selection.getMinY(); y <= selection.getMaxY(); y++) { + for (int x = 0; x <= 15; x++) { + for (int z = 0; z <= 15; z++) { + Block block = chunk.getBlock(x, y, z); + if (!selection.contains(block.getLocation().toVector())) continue; + block.setBlockData(blockToSet); + } + } + } + }); + } + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/util/ChunkCoords.java b/src/main/java/me/youhavetrouble/blockedit/util/ChunkCoords.java new file mode 100644 index 0000000..9011ce4 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/util/ChunkCoords.java @@ -0,0 +1,19 @@ +package me.youhavetrouble.blockedit.util; + +public class ChunkCoords { + + private final int x, z; + + public ChunkCoords(int x, int z) { + this.x = x; + this.z = z; + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java b/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java index b1e60d2..fd5e12b 100644 --- a/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java +++ b/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java @@ -1,31 +1,30 @@ package me.youhavetrouble.blockedit.util; +import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.util.BoundingBox; +import java.util.concurrent.CompletableFuture; + public class ChunkWork { - private final int x, z, minHeight, maxHeight; + private int x, z; - public ChunkWork(double x, double z, World world) { - this.x = (int) x; - this.z = (int) z; - if (world == null) { - this.maxHeight = 256; - this.minHeight = 0; - } else { - this.minHeight = world.getMinHeight(); - this.maxHeight = world.getMaxHeight(); - } + public ChunkWork(double x, double z) { + setCoords(x,z); } - public BoundingBox getWorkspace(BoundingBox selection) { + public BoundingBox getWorkspace(BoundingBox selection, World world) { // TODO make it return shared space of getChunkBox and selection to cull some of the blocks from iterations - return getChunkBox(); + return getChunkBox(world); } - private BoundingBox getChunkBox() { - return new BoundingBox(x*16, minHeight, z*16, (x+1)*16, maxHeight, (z+1)*16); + public CompletableFuture getChunkAsync(World world) { + return world.getChunkAtAsync(x,z, true); + } + + private BoundingBox getChunkBox(World world) { + return new BoundingBox(x*16, world.getMinHeight(), z*16, (x+1)*16, world.getMaxHeight(), (z+1)*16); } public int getX() { @@ -35,4 +34,29 @@ public class ChunkWork { public int getZ() { return z; } + + public void setCoords(double x, double z) { + this.x = (int) x >> 4; + this.z = (int) z >> 4; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof ChunkWork chunkWork)) return false; + return this.x == chunkWork.getX() && this.z == chunkWork.getZ(); + } + + @Override + public int hashCode() { + int res = 17; + res = res * 31 + Math.min(x, z); + res = res * 31 + Math.max(x, z); + return res; + } + + public ChunkWork clone() { + return new ChunkWork(x*16,z*16); + } + + } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 87f065c..4c7b3f1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,4 +10,7 @@ commands: description: test /wand: permission: blockedit.wand - description: gives you a blockedit wand + description: Gives you a blockedit wand + /set: + permission: blockedit.set + description: Set selection to chosen block diff --git a/src/test/java/WorkSplitterTest.java b/src/test/java/WorkSplitterTest.java index dbd97d7..f825ab6 100644 --- a/src/test/java/WorkSplitterTest.java +++ b/src/test/java/WorkSplitterTest.java @@ -12,11 +12,11 @@ public class WorkSplitterTest { */ @Test public void testLocationToChunk() { - ChunkWork testOne = new ChunkWork(0,0, null); - ChunkWork workSplitterTestOne = WorkSplitter.locationToChunkWork(15, 15, null); + ChunkWork testOne = new ChunkWork(0,0); + ChunkWork workSplitterTestOne = WorkSplitter.locationToChunkWork(15, 15); - ChunkWork testTwo = WorkSplitter.locationToChunkWork(10.233D, -138.788D, null); - ChunkWork workSplitterTestTwo = new ChunkWork(0,-9, null); + ChunkWork testTwo = WorkSplitter.locationToChunkWork(10.233D, -138.788D); + ChunkWork workSplitterTestTwo = new ChunkWork(0,-9); assertEquals(workSplitterTestOne.getX(), testOne.getX()); assertEquals(workSplitterTestOne.getZ(), testOne.getZ());