commit 9dbcc6d7d079fc562b7630e847f46fedb9d13431 Author: YouHaveTrouble Date: Thu Jul 22 03:34:32 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..76d7dcc --- /dev/null +++ b/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + me.youhavetrouble + BlockEdit + 1.0-SNAPSHOT + jar + + BlockEdit + + Modern WorldEdit alternative + + 16 + UTF-8 + + youhavetrouble.me + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://papermc.io/repo/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + io.papermc.paper + paper-api + 1.17.1-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java b/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java new file mode 100644 index 0000000..d428372 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/BEPlayer.java @@ -0,0 +1,66 @@ +package me.youhavetrouble.blockedit; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.BoundingBox; + +import java.util.HashMap; +import java.util.UUID; + +public class BEPlayer { + + private static final HashMap playerHashMap = new HashMap<>(); + private BoundingBox selection; + private Location selectionPoint1, selectionPoint2; + + public BoundingBox getSelection() { + return selection; + } + + private void updateSelection() { + if (selectionPoint1 == null || selectionPoint2 == null) return; + selection = BoundingBox.of(selectionPoint1, selectionPoint2); + } + + public void setSelectionPoint1(Location selectionPoint1) { + if (this.selectionPoint1 != null && this.selectionPoint1.equals(selectionPoint1)) return; + this.selectionPoint1 = selectionPoint1; + updateSelection(); + } + + public void setSelectionPoint2(Location selectionPoint2) { + if (this.selectionPoint2 != null && this.selectionPoint2.equals(selectionPoint2)) return; + this.selectionPoint2 = selectionPoint2; + updateSelection(); + } + + /** + * @return Clone of selectionPoint1 + */ + public Location getSelectionPoint1() { + return selectionPoint1.clone(); + } + + /** + * @return Clone of selectionPoint2 + */ + public Location getSelectionPoint2() { + return selectionPoint2.clone(); + } + + protected static void addPlayer(Player player) { + playerHashMap.put(player.getUniqueId(), new BEPlayer()); + } + + protected static void removePlayer(Player player) { + playerHashMap.remove(player.getUniqueId()); + } + + public static BEPlayer getByPlayer(Player player) { + return getByUuid(player.getUniqueId()); + } + + protected static BEPlayer getByUuid(UUID uuid) { + return playerHashMap.get(uuid); + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java new file mode 100644 index 0000000..e134585 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java @@ -0,0 +1,34 @@ +package me.youhavetrouble.blockedit; + +import me.youhavetrouble.blockedit.api.BlockEditWands; +import me.youhavetrouble.blockedit.commands.WandCommand; +import me.youhavetrouble.blockedit.wands.SelectionWand; +import org.bukkit.plugin.java.JavaPlugin; + +public final class BlockEdit extends JavaPlugin { + + private static BlockEdit plugin; + + @Override + public void onEnable() { + plugin = this; + + getServer().getPluginManager().registerEvents(new JoinLeaveListener(), this); + + SelectionWand selectionWand = new SelectionWand(); + BlockEditWands.registerWand(selectionWand); + getServer().getPluginManager().registerEvents(selectionWand, this); + + getCommand("test").setExecutor(new TestCommand()); + + WandCommand wandCommand = new WandCommand(); + getCommand("/wand").setExecutor(wandCommand); + getCommand("/wand").setTabCompleter(wandCommand); + + } + + + public static BlockEdit getPlugin() { + return plugin; + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/JoinLeaveListener.java b/src/main/java/me/youhavetrouble/blockedit/JoinLeaveListener.java new file mode 100644 index 0000000..d519aa2 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/JoinLeaveListener.java @@ -0,0 +1,20 @@ +package me.youhavetrouble.blockedit; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class JoinLeaveListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerJoin(PlayerJoinEvent event) { + BEPlayer.addPlayer(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerQuit(PlayerQuitEvent event) { + BEPlayer.removePlayer(event.getPlayer()); + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/TestCommand.java b/src/main/java/me/youhavetrouble/blockedit/TestCommand.java new file mode 100644 index 0000000..3a31e33 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/TestCommand.java @@ -0,0 +1,41 @@ +package me.youhavetrouble.blockedit; + +import me.youhavetrouble.blockedit.util.ChunkWork; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BoundingBox; +import org.jetbrains.annotations.NotNull; + +public class TestCommand implements CommandExecutor { + + BukkitTask task; + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (sender instanceof Player player) { + + Location location = player.getLocation(); + ChunkWork work = WorkSplitter.locationToChunkWork(location.getX(), location.getZ(), location.getWorld()); + if (task != null) { + task.cancel(); + } + task = Bukkit.getScheduler().runTaskTimerAsynchronously(BlockEdit.getPlugin(),() -> { + BoundingBox box = work.getWorkspace(null); + 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); + } + }, 0, 4); + + + + } + return true; + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java b/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java new file mode 100644 index 0000000..0f10fa7 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/WorkSplitter.java @@ -0,0 +1,30 @@ +package me.youhavetrouble.blockedit; + +import me.youhavetrouble.blockedit.util.ChunkWork; +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) { + 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); + if (chunks.contains(chunkWork)) continue; + chunks.add(chunkWork); + } + } + return chunks; + } + + public static ChunkWork locationToChunkWork(double x, double z, World world) { + int chunkX = (int) x >> 4; + int chunkZ = (int) z >> 4; + return new ChunkWork(chunkX, chunkZ, world); + } + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWand.java b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWand.java new file mode 100644 index 0000000..5f078f0 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWand.java @@ -0,0 +1,28 @@ +package me.youhavetrouble.blockedit.api; + + +import net.kyori.adventure.text.Component; + +public interface BlockEditWand { + + /** + * A unique id to identify the wand. Also used in //wand command. + */ + String getId(); + + /** + * Name of the wand that will be used as wand item name. + */ + Component getName(); + + /** + * Custom model data for the wand item. Set to 0 to not give the wand custom model data. + */ + int getCustomModelData(); + + /** + * Permission for the wand usage + */ + String getPermission(); + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWands.java b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWands.java new file mode 100644 index 0000000..619c753 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditWands.java @@ -0,0 +1,70 @@ +package me.youhavetrouble.blockedit.api; + +import com.google.common.collect.ImmutableSet; +import me.youhavetrouble.blockedit.BlockEdit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Collection; +import java.util.HashMap; + +public class BlockEditWands { + + private static final NamespacedKey wandKey = new NamespacedKey(BlockEdit.getPlugin(), "wand"); + private static final HashMap wands = new HashMap<>(); + + public static NamespacedKey getWandKey() { + return wandKey; + } + + /** + * @param itemStack ItemStack to check + * @return WandId if a wand, null otherwise + */ + public static String isWand(ItemStack itemStack) { + if (itemStack == null) return null; + if (itemStack.getItemMeta() == null) return null; + if (!itemStack.getItemMeta().getPersistentDataContainer().has(BlockEditWands.getWandKey(), PersistentDataType.STRING)) return null; + return itemStack.getItemMeta().getPersistentDataContainer().get(BlockEditWands.getWandKey(), PersistentDataType.STRING); + } + + /** + * @return Immutable set of registered wand IDs + */ + public static Collection getWandIds() { + return ImmutableSet.copyOf(wands.keySet()); + } + + public static ItemStack getWand(String wandId) { + BlockEditWand wand = wands.get(wandId); + if (wand == null) return null; + ItemStack itemStack = new ItemStack(Material.WOODEN_AXE); + ItemMeta meta = itemStack.getItemMeta(); + if (wand.getName() != null) + meta.displayName(wand.getName()); + if (wand.getCustomModelData() != 0) + meta.setCustomModelData(wand.getCustomModelData()); + meta.getPersistentDataContainer().set(BlockEditWands.getWandKey(), PersistentDataType.STRING, wandId); + itemStack.setItemMeta(meta); + return itemStack; + } + + + /** + * PSA: Wand IDs will get converted to lowercase. + * @return true if registered successfully, false if not + */ + public static boolean registerWand(BlockEditWand wand) { + if (wands.containsKey(wand.getId().toLowerCase())) { + BlockEdit.getPlugin().getLogger().warning("Tried to register wand with id \""+wand.getId()+"\", but wand with that id already exists!"); + return false; + } + wands.put(wand.getId().toLowerCase(), wand); + return true; + } + + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/commands/WandCommand.java b/src/main/java/me/youhavetrouble/blockedit/commands/WandCommand.java new file mode 100644 index 0000000..e3dfcab --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/commands/WandCommand.java @@ -0,0 +1,44 @@ +package me.youhavetrouble.blockedit.commands; + +import me.youhavetrouble.blockedit.api.BlockEditWands; +import net.kyori.adventure.text.Component; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +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 WandCommand 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; + ItemStack wand; + if (args.length == 0) { + wand = BlockEditWands.getWand("select"); + if (wand == null) return true; + } else { + wand = BlockEditWands.getWand(args[0]); + if (wand == null) { + player.sendMessage(Component.text("Could not find wand with id "+args[0])); + return true; + } + } + player.getInventory().addItem(wand); + return true; + } + + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (args.length == 1) + return StringUtil.copyPartialMatches(args[0], BlockEditWands.getWandIds(), new ArrayList<>()); + return null; + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java b/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java new file mode 100644 index 0000000..b1e60d2 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/util/ChunkWork.java @@ -0,0 +1,38 @@ +package me.youhavetrouble.blockedit.util; + +import org.bukkit.World; +import org.bukkit.util.BoundingBox; + +public class ChunkWork { + + private final int x, z, minHeight, maxHeight; + + 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 BoundingBox getWorkspace(BoundingBox selection) { + // TODO make it return shared space of getChunkBox and selection to cull some of the blocks from iterations + return getChunkBox(); + } + + private BoundingBox getChunkBox() { + return new BoundingBox(x*16, minHeight, z*16, (x+1)*16, maxHeight, (z+1)*16); + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } +} diff --git a/src/main/java/me/youhavetrouble/blockedit/wands/SelectionWand.java b/src/main/java/me/youhavetrouble/blockedit/wands/SelectionWand.java new file mode 100644 index 0000000..78ed084 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/wands/SelectionWand.java @@ -0,0 +1,61 @@ +package me.youhavetrouble.blockedit.wands; + +import me.youhavetrouble.blockedit.BEPlayer; +import me.youhavetrouble.blockedit.api.BlockEditWand; +import me.youhavetrouble.blockedit.api.BlockEditWands; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; + +public class SelectionWand implements Listener, BlockEditWand { + + @Override + public String getId() { + return "select"; + } + + @Override + public Component getName() { + return Component.text("Selection Wand").decoration(TextDecoration.ITALIC, false); + } + + @Override + public int getCustomModelData() { + return 0; + } + + @Override + public String getPermission() { + return "blockedit.selectwand"; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerSelectBlock(PlayerInteractEvent event) { + Player player = event.getPlayer(); + String wandId = BlockEditWands.isWand(event.getItem()); + if (wandId == null) return; + if (!wandId.equals(getId())) return; + if (!player.hasPermission(getPermission())) return; + Block block = event.getClickedBlock(); + if (block == null) return; + Action action = event.getAction(); + if (action.equals(Action.LEFT_CLICK_BLOCK)) { + event.setCancelled(true); + BEPlayer.getByPlayer(player).setSelectionPoint1(block.getLocation()); + player.sendMessage(Component.text("First point set")); + return; + } + if (action.equals(Action.RIGHT_CLICK_BLOCK)) { + event.setCancelled(true); + BEPlayer.getByPlayer(player).setSelectionPoint2(block.getLocation()); + player.sendMessage(Component.text("Second point set")); + return; + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..87f065c --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,13 @@ +name: BlockEdit +version: ${project.version} +main: me.youhavetrouble.blockedit.BlockEdit +api-version: 1.17 +authors: [ YouHaveTrouble ] +description: Modern WorldEdit alternative +website: youhavetrouble.me +commands: + test: + description: test + /wand: + permission: blockedit.wand + description: gives you a blockedit wand diff --git a/src/test/java/WorkSplitterTest.java b/src/test/java/WorkSplitterTest.java new file mode 100644 index 0000000..dbd97d7 --- /dev/null +++ b/src/test/java/WorkSplitterTest.java @@ -0,0 +1,28 @@ +import me.youhavetrouble.blockedit.WorkSplitter; +import me.youhavetrouble.blockedit.util.ChunkWork; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WorkSplitterTest { + + + /** + * Test if location -> chunk location works properly + */ + @Test + public void testLocationToChunk() { + ChunkWork testOne = new ChunkWork(0,0, null); + ChunkWork workSplitterTestOne = WorkSplitter.locationToChunkWork(15, 15, null); + + ChunkWork testTwo = WorkSplitter.locationToChunkWork(10.233D, -138.788D, null); + ChunkWork workSplitterTestTwo = new ChunkWork(0,-9, null); + + assertEquals(workSplitterTestOne.getX(), testOne.getX()); + assertEquals(workSplitterTestOne.getZ(), testOne.getZ()); + + assertEquals(workSplitterTestTwo.getX(), testTwo.getX()); + assertEquals(workSplitterTestTwo.getZ(), testTwo.getZ()); + } + +}