first working version of tunnelling

This commit is contained in:
2026-09-27 21:18:23 +02:00
parent b573192990
commit 8616a74f33
6 changed files with 169 additions and 32 deletions
@@ -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);
@@ -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<Key, Set<TagEntry<BlockType>>> 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());
@@ -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<BlockType> tagKey = BlockTypeTagKeys.create(key);
TagEntry<BlockType> tagEntry = TagEntry.tagEntry(tagKey);
blockTagKeys.add(tagEntry);
} catch (IllegalArgumentException ignored) {
} catch (IllegalArgumentException e) {
}
continue;
}
try {
Key key = Key.key(blockTag);
TagKey<BlockType> tagKey = BlockTypeTagKeys.create(key);
TagEntry<BlockType> tagEntry = TagEntry.tagEntry(tagKey);
TypedKey<BlockType> typedKey = TypedKey.create(RegistryKey.BLOCK, key);
TagEntry<BlockType> tagEntry = TagEntry.valueEntry(typedKey);
blockTagKeys.add(tagEntry);
} catch (IllegalArgumentException | NullPointerException ignored) {
} catch (IllegalArgumentException | NullPointerException e) {
}
}
return blockTagKeys;
@@ -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<Key, Set<TagEntry<BlockType>>> getBlockTagsToRegister() {
return ImmutableMap.of();
}
static @Nullable ItemStack findFirstWithEnchant(
@NotNull EntityEquipment equipment,
@NotNull Enchantment enchantment
@@ -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<TagEntry<ItemType>> supportedItemTags = new HashSet<>();
private final Set<TagKey<Enchantment>> enchantTagKeys = new HashSet<>();
private final Set<TagEntry<BlockType>> blockTagKeys = new HashSet<>();
private final Map<Key, Set<TagEntry<BlockType>>> affectedBlockTags = new HashMap<>();
public TunnellingEnchant(
private TunnellingEnchant(
int anvilCost,
int weight,
EnchantmentRegistryEntry.EnchantmentCost minimumCost,
EnchantmentRegistryEntry.EnchantmentCost maximumCost,
Collection<TagKey<Enchantment>> enchantTagKeys,
Collection<TagEntry<ItemType>> supportedItemTags,
Collection<TagEntry<BlockType>> supportedBlockTags,
int maxLevel
Map<Key, Set<TagEntry<BlockType>>> 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<Key, Set<TagEntry<BlockType>>> 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"
)
)),
Map.of(
AFFECTED_BLOCKS_KEY,
EnchantioConfig.getBlockTagKeysFromList(EnchantioConfig.getStringList(
configurationSection,
"mineableBlockTags",
"affectedBlockTags",
List.of(
"#minecraft:base_stone_overworld",
"#minecraft:base_stone_nether",
"#minecraft:ores",
"minecraft:end_stone"
)
)),
EnchantioConfig.getInt(configurationSection, "maxLevel", 1)
))
),
EnchantioConfig.getInt(configurationSection, "maxLevel", 1),
EnchantioConfig.getInt(configurationSection, "blocksPerLevel", 1),
EnchantioConfig.getBoolean(configurationSection, "canBreakToolIncompatibleBlocks", false)
);
if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) {
@@ -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<Block> 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<Block> 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<Block> getSquare(Block block, BlockFace face, int range) {
Set<Block> 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<Block> getBlocksToTunnel(Location base, int changeX, int changeY, int changeZ) {
List<Block> 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());
}
}