Compare commits

...
2 Commits
Author SHA1 Message Date
YouHaveTrouble 8616a74f33 first working version of tunnelling 2026-09-27 21:18:23 +02:00
YouHaveTrouble b573192990 some work on tunnelling enchant 2026-09-25 19:43:45 +02:00
6 changed files with 375 additions and 0 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());
@@ -2,12 +2,14 @@ package me.youhavetrouble.enchantio;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys;
import io.papermc.paper.registry.keys.tags.EnchantmentTagKeys;
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.*;
import net.kyori.adventure.key.Key;
import org.bukkit.block.BlockType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -78,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);
@@ -210,6 +215,32 @@ public class EnchantioConfig {
return enchantTagKeys;
}
public static Set<TagEntry<BlockType>> getBlockTagKeysFromList(@NotNull List<String> tags) {
Set<TagEntry<BlockType>> blockTagKeys = new HashSet<>();
for (String blockTag : tags) {
if (blockTag == null) continue;
if (blockTag.startsWith("#")) {
blockTag = blockTag.substring(1);
try {
Key key = Key.key(blockTag);
TagKey<BlockType> tagKey = BlockTypeTagKeys.create(key);
TagEntry<BlockType> tagEntry = TagEntry.tagEntry(tagKey);
blockTagKeys.add(tagEntry);
} catch (IllegalArgumentException e) {
}
continue;
}
try {
Key key = Key.key(blockTag);
TypedKey<BlockType> typedKey = TypedKey.create(RegistryKey.BLOCK, key);
TagEntry<BlockType> tagEntry = TagEntry.valueEntry(typedKey);
blockTagKeys.add(tagEntry);
} catch (IllegalArgumentException | NullPointerException e) {
}
}
return blockTagKeys;
}
public static ConfigurationSection getConfigSection(ConfigurationSection section, String key) {
ConfigurationSection value = section.getConfigurationSection(key);
if (value == null) {
@@ -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
@@ -0,0 +1,171 @@
package me.youhavetrouble.enchantio.enchants;
import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.tag.TagEntry;
import me.youhavetrouble.enchantio.EnchantioConfig;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import org.bukkit.block.BlockType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.EquipmentSlotGroup;
import org.bukkit.inventory.ItemType;
import org.jetbrains.annotations.NotNull;
import java.util.*;
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 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 Map<Key, Set<TagEntry<BlockType>>> affectedBlockTags = new HashMap<>();
private TunnellingEnchant(
int anvilCost,
int weight,
EnchantmentRegistryEntry.EnchantmentCost minimumCost,
EnchantmentRegistryEntry.EnchantmentCost maximumCost,
Collection<TagKey<Enchantment>> enchantTagKeys,
Collection<TagEntry<ItemType>> supportedItemTags,
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.affectedBlockTags.putAll(affectedBlockTags);
}
@Override
public @NotNull Key getKey() {
return KEY;
}
@Override
public @NotNull Component getDescription() {
return Component.translatable("enchantio.enchant.tunnelling", "Tunnelling");
}
@Override
public int getAnvilCost() {
return anvilCost;
}
@Override
public int getMaxLevel() {
return maxLevel;
}
@Override
public int getWeight() {
return weight;
}
@Override
public EnchantmentRegistryEntry.@NotNull EnchantmentCost getMinimumCost() {
return minimumCost;
}
@Override
public EnchantmentRegistryEntry.@NotNull EnchantmentCost getMaximumCost() {
return maximumCost;
}
@Override
public @NotNull Iterable<EquipmentSlotGroup> getActiveSlots() {
return Set.of(EquipmentSlotGroup.MAINHAND);
}
@Override
public @NotNull Set<TagEntry<ItemType>> getSupportedItems() {
return supportedItemTags;
}
@Override
public @NotNull Set<TagKey<Enchantment>> getEnchantTagKeys() {
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(
EnchantioConfig.getInt(configurationSection, "anvilCost", 1),
EnchantioConfig.getInt(configurationSection, "weight", 10),
EnchantmentRegistryEntry.EnchantmentCost.of(
EnchantioConfig.getInt(configurationSection, "minimumCost.base", 40),
EnchantioConfig.getInt(configurationSection, "minimumCost.additionalPerLevel", 3)
),
EnchantmentRegistryEntry.EnchantmentCost.of(
EnchantioConfig.getInt(configurationSection, "maximumCost.base", 65),
EnchantioConfig.getInt(configurationSection, "maximumCost.additionalPerLevel", 1)
),
EnchantioConfig.getEnchantmentTagKeysFromList(EnchantioConfig.getStringList(
configurationSection,
"enchantmentTags",
List.of("#in_enchanting_table")
)),
EnchantioConfig.getItemTagEntriesFromList(EnchantioConfig.getStringList(
configurationSection,
"supportedItemTags",
List.of(
"#minecraft:enchantable/mining"
)
)),
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)) {
ENCHANTS.put(TunnellingEnchant.KEY, tunnellingEnchant);
}
return tunnellingEnchant;
}
}
@@ -0,0 +1,140 @@
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.*;
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;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
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 tunnellingBlockFaceKey = new NamespacedKey("enchantio", "tunellingblockface");
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTunnelBlockSideCheck(PlayerInteractEvent event) {
if (tunnelling == null) 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;
ItemStack item = event.getItem();
if (item == null) return;
if (!item.containsEnchantment(tunnelling)) return;
event.getPlayer().getPersistentDataContainer().set(tunnellingBlockFaceKey, PersistentDataType.STRING, event.getBlockFace().toString());
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onTunnelBlockBreak(BlockBreakEvent event) {
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();
int enchantLevel = item.getEnchantmentLevel(tunnelling);
if (enchantLevel <= 0) return;
Block block = event.getBlock();
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());
}
}