diff --git a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java index 250d43f..047e4ec 100644 --- a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java +++ b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java @@ -49,6 +49,9 @@ public final class Enchantio extends JavaPlugin { if (EnchantioConfig.ENCHANTS.containsKey(WardEnchant.KEY)) { getServer().getPluginManager().registerEvents(new WardListener(), this); } + if (EnchantioConfig.ENCHANTS.containsKey(TunnellingEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new TunnellingListener(), this); + } if (EnchantioConfig.ENCHANTS.containsKey(PanicEnchant.KEY)) { getServer().getPluginManager().registerEvents(new PanicListener(), this); diff --git a/src/main/java/me/youhavetrouble/enchantio/EnchantioBootstrap.java b/src/main/java/me/youhavetrouble/enchantio/EnchantioBootstrap.java index abd1cf2..aa21951 100644 --- a/src/main/java/me/youhavetrouble/enchantio/EnchantioBootstrap.java +++ b/src/main/java/me/youhavetrouble/enchantio/EnchantioBootstrap.java @@ -6,14 +6,20 @@ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.event.RegistryEvents; +import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import io.papermc.paper.registry.tag.TagKey; +import io.papermc.paper.tag.TagEntry; import me.youhavetrouble.enchantio.enchants.EnchantioEnchant; +import net.kyori.adventure.key.Key; +import org.bukkit.block.BlockType; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Collection; +import java.util.Map; import java.util.Set; @SuppressWarnings("UnstableApiUsage") @@ -42,6 +48,18 @@ public class EnchantioBootstrap implements PluginBootstrap { } })); + context.getLifecycleManager().registerEventHandler(LifecycleEvents.TAGS.preFlatten(RegistryKey.BLOCK).newHandler((event) -> { + for (EnchantioEnchant enchant : enchantioEnchants) { + for (Map.Entry>> entry : enchant.getBlockTagsToRegister().entrySet()) { + logger.info("Registering block tag {}", entry.getKey()); + event.registrar().addToTag( + BlockTypeTagKeys.create(entry.getKey()), + entry.getValue() + ); + } + } + })); + context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.compose().newHandler(event -> { for (EnchantioEnchant enchant : enchantioEnchants) { logger.info("Registering enchantment {}", enchant.getKey()); diff --git a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java index f04e572..0445dd6 100644 --- a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java +++ b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java @@ -80,6 +80,9 @@ public class EnchantioConfig { ConfigurationSection wardSection = getConfigSection(enchantsSection, "ward"); WardEnchant.create(wardSection); + ConfigurationSection tunnellingSection = getConfigSection(enchantsSection, "tunnelling"); + TunnellingEnchant.create(tunnellingSection); + ConfigurationSection cursesSection = getConfigSection(configuration, "curses"); migrateEnchantTags(cursesSection); @@ -223,16 +226,16 @@ public class EnchantioConfig { TagKey tagKey = BlockTypeTagKeys.create(key); TagEntry tagEntry = TagEntry.tagEntry(tagKey); blockTagKeys.add(tagEntry); - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException e) { } continue; } try { Key key = Key.key(blockTag); - TagKey tagKey = BlockTypeTagKeys.create(key); - TagEntry tagEntry = TagEntry.tagEntry(tagKey); + TypedKey typedKey = TypedKey.create(RegistryKey.BLOCK, key); + TagEntry tagEntry = TagEntry.valueEntry(typedKey); blockTagKeys.add(tagEntry); - } catch (IllegalArgumentException | NullPointerException ignored) { + } catch (IllegalArgumentException | NullPointerException e) { } } return blockTagKeys; diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java index b82debc..342efb5 100644 --- a/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java @@ -1,5 +1,6 @@ package me.youhavetrouble.enchantio.enchants; +import com.google.common.collect.ImmutableMap; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; @@ -7,6 +8,7 @@ import io.papermc.paper.registry.tag.TagKey; import io.papermc.paper.tag.TagEntry; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import org.bukkit.block.BlockType; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.EquipmentSlotGroup; @@ -15,6 +17,8 @@ import org.bukkit.inventory.ItemType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; +import java.util.Map; import java.util.Set; @SuppressWarnings("UnstableApiUsage") @@ -57,6 +61,14 @@ public interface EnchantioEnchant { return TagEntry.valueEntry(TypedKey.create(RegistryKey.ENCHANTMENT, getKey())); } + /** + * Get block tags to register to use for this enchantment and what tags/blocks it should contain + */ + @NotNull + default Map>> getBlockTagsToRegister() { + return ImmutableMap.of(); + } + static @Nullable ItemStack findFirstWithEnchant( @NotNull EntityEquipment equipment, @NotNull Enchantment enchantment diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/TunnellingEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/TunnellingEnchant.java index 2e233a6..e8688cd 100644 --- a/src/main/java/me/youhavetrouble/enchantio/enchants/TunnellingEnchant.java +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/TunnellingEnchant.java @@ -20,33 +20,40 @@ import static me.youhavetrouble.enchantio.EnchantioConfig.ENCHANTS; @SuppressWarnings("UnstableApiUsage") public class TunnellingEnchant implements EnchantioEnchant { - public static final Key KEY = Key.key("enchantio:tunnelling"); + public static final Key KEY = Key.key("enchantio", "tunnelling"); - private final int anvilCost, weight, maxLevel; + public static final Key AFFECTED_BLOCKS_KEY = Key.key("enchantio", "affected_by_tunnelling"); + + private final int anvilCost, weight, maxLevel, blocksPerLevel; + private final boolean canBreakToolIncompatibleBlocks; private final EnchantmentRegistryEntry.EnchantmentCost minimumCost; private final EnchantmentRegistryEntry.EnchantmentCost maximumCost; private final Set> supportedItemTags = new HashSet<>(); private final Set> enchantTagKeys = new HashSet<>(); - private final Set> blockTagKeys = new HashSet<>(); + private final Map>> affectedBlockTags = new HashMap<>(); - public TunnellingEnchant( + private TunnellingEnchant( int anvilCost, int weight, EnchantmentRegistryEntry.EnchantmentCost minimumCost, EnchantmentRegistryEntry.EnchantmentCost maximumCost, Collection> enchantTagKeys, Collection> supportedItemTags, - Collection> supportedBlockTags, - int maxLevel + Map>> affectedBlockTags, + int maxLevel, + int blocksPerLevel, + boolean canBreakToolIncompatibleBlocks ) { this.anvilCost = anvilCost; this.weight = weight; this.minimumCost = minimumCost; this.maximumCost = maximumCost; this.maxLevel = maxLevel; + this.blocksPerLevel = blocksPerLevel; + this.canBreakToolIncompatibleBlocks = canBreakToolIncompatibleBlocks; this.supportedItemTags.addAll(supportedItemTags); this.enchantTagKeys.addAll(enchantTagKeys); - this.blockTagKeys.addAll(supportedBlockTags); + this.affectedBlockTags.putAll(affectedBlockTags); } @Override @@ -99,6 +106,18 @@ public class TunnellingEnchant implements EnchantioEnchant { return Collections.unmodifiableSet(enchantTagKeys); } + @Override + public @NotNull Map>> getBlockTagsToRegister() { + return affectedBlockTags; + } + + public int getBlocksPerLevel() { + return blocksPerLevel; + } + + public boolean canBreakToolIncompatibleBlocks() { + return canBreakToolIncompatibleBlocks; + } public static TunnellingEnchant create(ConfigurationSection configurationSection) { TunnellingEnchant tunnellingEnchant = new TunnellingEnchant( @@ -124,17 +143,22 @@ public class TunnellingEnchant implements EnchantioEnchant { "#minecraft:enchantable/mining" ) )), - EnchantioConfig.getBlockTagKeysFromList(EnchantioConfig.getStringList( - configurationSection, - "mineableBlockTags", - List.of( - "#minecraft:base_stone_overworld", - "#minecraft:base_stone_nether", - "#minecraft:ores", - "minecraft:end_stone" - ) - )), - EnchantioConfig.getInt(configurationSection, "maxLevel", 1) + Map.of( + AFFECTED_BLOCKS_KEY, + EnchantioConfig.getBlockTagKeysFromList(EnchantioConfig.getStringList( + configurationSection, + "affectedBlockTags", + List.of( + "#minecraft:base_stone_overworld", + "#minecraft:base_stone_nether", + "#minecraft:ores", + "minecraft:end_stone" + ) + )) + ), + EnchantioConfig.getInt(configurationSection, "maxLevel", 1), + EnchantioConfig.getInt(configurationSection, "blocksPerLevel", 1), + EnchantioConfig.getBoolean(configurationSection, "canBreakToolIncompatibleBlocks", false) ); if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) { diff --git a/src/main/java/me/youhavetrouble/enchantio/listeners/TunnellingListener.java b/src/main/java/me/youhavetrouble/enchantio/listeners/TunnellingListener.java index fea796e..220dd67 100644 --- a/src/main/java/me/youhavetrouble/enchantio/listeners/TunnellingListener.java +++ b/src/main/java/me/youhavetrouble/enchantio/listeners/TunnellingListener.java @@ -3,13 +3,14 @@ package me.youhavetrouble.enchantio.listeners; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys; +import me.youhavetrouble.enchantio.Enchantio; import me.youhavetrouble.enchantio.EnchantioConfig; import me.youhavetrouble.enchantio.enchants.EnchantioEnchant; import me.youhavetrouble.enchantio.enchants.TunnellingEnchant; -import org.bukkit.GameMode; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; +import org.bukkit.*; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockType; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -21,19 +22,23 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @SuppressWarnings("UnstableApiUsage") public class TunnellingListener implements Listener { + private final static Set blockBreakSkips = new HashSet<>(); private final Enchantment tunnelling = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(TunnellingEnchant.KEY); - private final NamespacedKey tunnellingKey = new NamespacedKey("enchantio", "tunnelling"); private final NamespacedKey tunnellingBlockFaceKey = new NamespacedKey("enchantio", "tunellingblockface"); @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onTunnelBlockSideCheck(PlayerInteractEvent event) { if (tunnelling == null) return; - if (event.getHand() != EquipmentSlot.OFF_HAND) return; + if (event.getHand() != EquipmentSlot.HAND) return; if (event.getAction() != Action.LEFT_CLICK_BLOCK) return; Player player = event.getPlayer(); if (player.isSneaking() || GameMode.CREATIVE.equals(player.getGameMode())) return; @@ -48,16 +53,88 @@ public class TunnellingListener implements Listener { if (tunnelling == null) return; Player player = event.getPlayer(); if (GameMode.CREATIVE.equals(player.getGameMode())) return; + if (player.isSneaking()) return; + String rawFace = player.getPersistentDataContainer().get(tunnellingBlockFaceKey, PersistentDataType.STRING); + if (rawFace == null) return; EnchantioEnchant enchant = EnchantioConfig.ENCHANTS.get(TunnellingEnchant.KEY); if (!(enchant instanceof TunnellingEnchant tunnellingEnchant)) return; ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); - if (!item.containsEnchantment(tunnelling)) return; + int enchantLevel = item.getEnchantmentLevel(tunnelling); + if (enchantLevel <= 0) return; Block block = event.getBlock(); - // TODO register a block tag and flatten it to check the block against it - Registry.BLOCK.getTagValues(BlockTypeTagKeys.create(tunnellingKey)).contains(block.getType().asBlockType()); - - + BlockType blockType = block.getType().asBlockType(); + if (!Registry.BLOCK.getTagValues(BlockTypeTagKeys.create(TunnellingEnchant.AFFECTED_BLOCKS_KEY)).contains(blockType)) return; + if (blockBreakSkips.contains(block)) return; + BlockFace blockFace; + try { + blockFace = BlockFace.valueOf(rawFace); + } catch (IllegalArgumentException e) { + return; + } + Set blocksToBreak = getSquare(block, blockFace, tunnellingEnchant.getBlocksPerLevel() * enchantLevel); + blockBreakSkips.addAll(blocksToBreak); + for (Block b : blocksToBreak) { + BlockBreakEvent breakEvent = new BlockBreakEvent(b, player); + Enchantio.getPlugin(Enchantio.class).getServer().getPluginManager().callEvent(breakEvent); + blockBreakSkips.remove(b); + breakBlock(breakEvent, tunnellingEnchant); + } } + private Set getSquare(Block block, BlockFace face, int range) { + Set blocks = new HashSet<>(); + switch (face) { + case DOWN: + case UP: + blocks.addAll(getBlocksToTunnel(block.getLocation(), range, 0, range)); + break; + case EAST: + case WEST: + blocks.addAll(getBlocksToTunnel(block.getLocation(), 0, range, range)); + break; + case NORTH: + case SOUTH: + blocks.addAll(getBlocksToTunnel(block.getLocation(), range, range, 0)); + break; + default: + break; + } + return blocks; + } + + /** + * Get all blocks that will be broken by Tunnelling enchantment + * @param base location at the centre of selection + * @return All blocks that will get broken + */ + private List getBlocksToTunnel(Location base, int changeX, int changeY, int changeZ) { + List blocks = new ArrayList<>(); + for (int x = (base.getBlockX() - changeX); x <= (base.getBlockX() + changeX); x++) { + for (int y = (base.getBlockY() - changeY); y <= (base.getBlockY() + changeY); y++) { + for (int z = (base.getBlockZ() - changeZ); z <= (base.getBlockZ() + changeZ); z++) { + Location loc = new Location(base.getWorld(), x, y, z); + if (base.equals(loc)) continue; // ignore the base block + Block block = loc.getBlock(); + BlockType blockType = block.getType().asBlockType(); + if (!Registry.BLOCK.getTagValues(BlockTypeTagKeys.create(TunnellingEnchant.AFFECTED_BLOCKS_KEY)).contains(blockType)) continue; + if (block.getType().isAir()) continue; + blocks.add(block); + } + } + } + return blocks; + } + + private void breakBlock(BlockBreakEvent event, TunnellingEnchant enchant) { + if (tunnelling == null) return; + if (event.isCancelled()) return; + Block block = event.getBlock(); + ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); + int enchantLevel = item.getEnchantmentLevel(tunnelling); + if (enchantLevel <= 0) return; + block.breakNaturally(item, true, event.getExpToDrop() > 0, enchant.canBreakToolIncompatibleBlocks()); + item.damage(1, event.getPlayer()); + } + }