Compare commits

..
8 Commits
5 changed files with 123 additions and 31 deletions
@@ -156,6 +156,26 @@ Cooldown in ticks that will be applied to the activating items cooldown group.
Namespaced key of the sound that will play when enchantment blocks a hit. Namespaced key of the sound that will play when enchantment blocks a hit.
## Tunnelling
### affectedBlockTags
- **Type**: [`Block Tags`](/configuration/input-types/#blocks)
List of blocks and block tags that will be affected by the enchantment's mining effect.
### blocksPerLevel
- **Type**: `int`
How many blocks in each direction enchantment should mine per enchantment level.
### visualizeBreaking
- **Type**: `boolean`
Display visualisation of blocks being broken by the enchantment along with breaking the base block.
## Panic ## Panic
### panicChancePerLevel ### panicChancePerLevel
@@ -73,6 +73,14 @@ Automatically blocks hits, but triggers a configurable cooldown every time it do
the enchanted items cooldown group, so if you make this enchantment support more than just 1 item, you're going to have the enchanted items cooldown group, so if you make this enchantment support more than just 1 item, you're going to have
to handle applying the cooldown group to them yourself. to handle applying the cooldown group to them yourself.
### Tunnelling
**Translation key**: `enchantio.enchantment.tunnelling`
**Description**:
Mines multiple blocks around initially destroyed block. Activates when mining affected block and breaks all the affected
blocks in a `range x range x 1` pattern.
## Curses ## Curses
### Curse of Panic ### Curse of Panic
+1 -1
View File
@@ -15,7 +15,7 @@
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>1.15.2</revision> <revision>1.16.0</revision>
</properties> </properties>
<url>https://youhavetrouble.me</url> <url>https://youhavetrouble.me</url>
@@ -25,6 +25,7 @@ public class TunnellingEnchant implements EnchantioEnchant {
public static final Key AFFECTED_BLOCKS_KEY = Key.key("enchantio", "affected_by_tunnelling"); public static final Key AFFECTED_BLOCKS_KEY = Key.key("enchantio", "affected_by_tunnelling");
private final int anvilCost, weight, maxLevel, blocksPerLevel; private final int anvilCost, weight, maxLevel, blocksPerLevel;
private final boolean visualizeBreaking;
private final EnchantmentRegistryEntry.EnchantmentCost minimumCost; private final EnchantmentRegistryEntry.EnchantmentCost minimumCost;
private final EnchantmentRegistryEntry.EnchantmentCost maximumCost; private final EnchantmentRegistryEntry.EnchantmentCost maximumCost;
private final Set<TagEntry<ItemType>> supportedItemTags = new HashSet<>(); private final Set<TagEntry<ItemType>> supportedItemTags = new HashSet<>();
@@ -40,7 +41,8 @@ public class TunnellingEnchant implements EnchantioEnchant {
Collection<TagEntry<ItemType>> supportedItemTags, Collection<TagEntry<ItemType>> supportedItemTags,
Map<Key, Set<TagEntry<BlockType>>> affectedBlockTags, Map<Key, Set<TagEntry<BlockType>>> affectedBlockTags,
int maxLevel, int maxLevel,
int blocksPerLevel int blocksPerLevel,
boolean visualizeBreaking
) { ) {
this.anvilCost = anvilCost; this.anvilCost = anvilCost;
this.weight = weight; this.weight = weight;
@@ -48,6 +50,7 @@ public class TunnellingEnchant implements EnchantioEnchant {
this.maximumCost = maximumCost; this.maximumCost = maximumCost;
this.maxLevel = maxLevel; this.maxLevel = maxLevel;
this.blocksPerLevel = blocksPerLevel; this.blocksPerLevel = blocksPerLevel;
this.visualizeBreaking = visualizeBreaking;
this.supportedItemTags.addAll(supportedItemTags); this.supportedItemTags.addAll(supportedItemTags);
this.enchantTagKeys.addAll(enchantTagKeys); this.enchantTagKeys.addAll(enchantTagKeys);
this.affectedBlockTags.putAll(affectedBlockTags); this.affectedBlockTags.putAll(affectedBlockTags);
@@ -112,6 +115,10 @@ public class TunnellingEnchant implements EnchantioEnchant {
return blocksPerLevel; return blocksPerLevel;
} }
public boolean shouldVisualizeBreaking() {
return visualizeBreaking;
}
public static TunnellingEnchant create(ConfigurationSection configurationSection) { public static TunnellingEnchant create(ConfigurationSection configurationSection) {
TunnellingEnchant tunnellingEnchant = new TunnellingEnchant( TunnellingEnchant tunnellingEnchant = new TunnellingEnchant(
EnchantioConfig.getInt(configurationSection, "anvilCost", 1), EnchantioConfig.getInt(configurationSection, "anvilCost", 1),
@@ -156,7 +163,8 @@ public class TunnellingEnchant implements EnchantioEnchant {
)) ))
), ),
EnchantioConfig.getInt(configurationSection, "maxLevel", 1), EnchantioConfig.getInt(configurationSection, "maxLevel", 1),
EnchantioConfig.getInt(configurationSection, "blocksPerLevel", 1) EnchantioConfig.getInt(configurationSection, "blocksPerLevel", 1),
EnchantioConfig.getBoolean(configurationSection, "visualizeBreaking", true)
); );
if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) { if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) {
@@ -3,8 +3,8 @@ package me.youhavetrouble.enchantio.listeners;
import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys; import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys;
import me.youhavetrouble.enchantio.Enchantio;
import me.youhavetrouble.enchantio.EnchantioConfig; import me.youhavetrouble.enchantio.EnchantioConfig;
import me.youhavetrouble.enchantio.enchants.EnchantioEnchant;
import me.youhavetrouble.enchantio.enchants.TunnellingEnchant; import me.youhavetrouble.enchantio.enchants.TunnellingEnchant;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@@ -15,60 +15,106 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.block.BlockDamageAbortEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType; import java.util.*;
import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
@SuppressWarnings("UnstableApiUsage") @SuppressWarnings("UnstableApiUsage")
public class TunnellingListener implements Listener { public class TunnellingListener implements Listener {
private final static Set<Block> blockBreakSkips = new HashSet<>(); private final static Set<Block> blockBreakSkips = new HashSet<>();
private final static Map<UUID, BlockBreakData> blockBreakData = new ConcurrentHashMap<>();
private final Enchantment tunnelling = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(TunnellingEnchant.KEY); private final Enchantment tunnelling = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(TunnellingEnchant.KEY);
private final NamespacedKey tunnellingBlockFaceKey = new NamespacedKey("enchantio", "tunellingblockface"); private final TunnellingEnchant tunnellingEnchant;
public TunnellingListener() {
if (EnchantioConfig.ENCHANTS.get(TunnellingEnchant.KEY) instanceof TunnellingEnchant enchant) {
this.tunnellingEnchant = enchant;
} else {
throw new RuntimeException("Tunnelling enchantment is not registered, but something tried to create a listener for it. Don't do that.");
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTunnelBlockSideCheck(PlayerInteractEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
if (!tunnellingEnchant.shouldVisualizeBreaking()) return;
event.getPlayer().getScheduler().runAtFixedRate(Enchantio.getPlugin(Enchantio.class), (task) -> {
Player player = Bukkit.getPlayer(uuid);
if (player == null || !player.isOnline()) {
task.cancel();
return;
}
BlockBreakData data = blockBreakData.get(uuid);
if (data == null) return;
long elapsedTicks = player.getWorld().getFullTime() - data.startedBreakingAtTick();
float breakSpeed = data.block.getBreakSpeed(player);
float ticksNeeded = 1.0f / breakSpeed;
float progress = Math.min(elapsedTicks / ticksNeeded, 1.0f);
if (progress >= 1.0f) return;
updateBlockBreakProgress(player, data, progress);
}, null, 1, 1);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTunnelBlockStartBreak(BlockDamageEvent event) {
if (tunnelling == null) return; if (tunnelling == null) return;
if (event.getHand() != EquipmentSlot.HAND) return;
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (GameMode.CREATIVE.equals(player.getGameMode())) return; if (GameMode.CREATIVE.equals(player.getGameMode())) return;
ItemStack item = event.getItem(); ItemStack item = player.getInventory().getItemInMainHand();
if (item == null) return; if (item.isEmpty()) return;
if (!item.containsEnchantment(tunnelling)) return; int enchantLevel = item.getEnchantmentLevel(tunnelling);
event.getPlayer().getPersistentDataContainer().set(tunnellingBlockFaceKey, PersistentDataType.STRING, event.getBlockFace().toString()); if (enchantLevel == -1) return;
if (blockBreakData.containsKey(player.getUniqueId())) return;
Block block = event.getBlock();
Set<Block> blocksToBreak = getSquare(block, event.getBlockFace(), tunnellingEnchant.getBlocksPerLevel() * enchantLevel);
blockBreakData.put(player.getUniqueId(), new BlockBreakData(block, block.getWorld().getFullTime(), event.getBlockFace(), blocksToBreak));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTunnelBlockStopBreak(BlockDamageAbortEvent event) {
BlockBreakData data = blockBreakData.remove(event.getPlayer().getUniqueId());
if (data == null) return;
updateBlockBreakProgress(event.getPlayer(), data, 0f);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
blockBreakData.remove(event.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerTpToAnotherWorld(PlayerTeleportEvent event) {
Player player = event.getPlayer();
BlockBreakData data = blockBreakData.get(player.getUniqueId());
if (data == null) return;
if (data.block.getWorld().getUID().equals(player.getWorld().getUID())) return;
blockBreakData.remove(player.getUniqueId());
} }
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onTunnelBlockBreak(BlockBreakEvent event) { public void onTunnelBlockBreak(BlockBreakEvent event) {
if (tunnelling == null) return; if (tunnelling == null) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
Block block = event.getBlock();
BlockBreakData data = blockBreakData.remove(player.getUniqueId());
if (blockBreakSkips.contains(block)) return;
if (data != null) updateBlockBreakProgress(player, data, 0f);
if (GameMode.CREATIVE.equals(player.getGameMode())) return; if (GameMode.CREATIVE.equals(player.getGameMode())) return;
if (player.isSneaking()) 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(); ItemStack item = event.getPlayer().getInventory().getItemInMainHand();
int enchantLevel = item.getEnchantmentLevel(tunnelling); int enchantLevel = item.getEnchantmentLevel(tunnelling);
if (enchantLevel <= 0) return; if (enchantLevel <= 0) return;
Block block = event.getBlock();
BlockType blockType = block.getType().asBlockType(); BlockType blockType = block.getType().asBlockType();
if (!Registry.BLOCK.getTagValues(BlockTypeTagKeys.create(TunnellingEnchant.AFFECTED_BLOCKS_KEY)).contains(blockType)) return; if (!Registry.BLOCK.getTagValues(BlockTypeTagKeys.create(TunnellingEnchant.AFFECTED_BLOCKS_KEY)).contains(blockType)) return;
if (blockBreakSkips.contains(block)) return; if (data == null) return;
BlockFace blockFace; Set<Block> blocksToBreak = getSquare(block, data.blockFace, tunnellingEnchant.getBlocksPerLevel() * enchantLevel);
try {
blockFace = BlockFace.valueOf(rawFace);
} catch (IllegalArgumentException e) {
return;
}
Set<Block> blocksToBreak = getSquare(block, blockFace, tunnellingEnchant.getBlocksPerLevel() * enchantLevel);
blockBreakSkips.addAll(blocksToBreak); blockBreakSkips.addAll(blocksToBreak);
for (Block b : blocksToBreak) { for (Block b : blocksToBreak) {
if (player.getInventory().getItemInMainHand().isEmpty()) { if (player.getInventory().getItemInMainHand().isEmpty()) {
@@ -81,6 +127,14 @@ public class TunnellingListener implements Listener {
} }
} }
private void updateBlockBreakProgress(Player player, BlockBreakData data, float progress) {
if (!tunnellingEnchant.shouldVisualizeBreaking()) return;
int id = Integer.MIN_VALUE;
for (Block block : data.blocksToSyncDamage) {
player.sendBlockDamage(block.getLocation(), progress, id++);
}
}
private Set<Block> getSquare(Block block, BlockFace face, int range) { private Set<Block> getSquare(Block block, BlockFace face, int range) {
switch (face) { switch (face) {
case DOWN, UP -> { case DOWN, UP -> {
@@ -119,4 +173,6 @@ public class TunnellingListener implements Listener {
return blocks; return blocks;
} }
private record BlockBreakData(Block block, long startedBreakingAtTick, BlockFace blockFace, Set<Block> blocksToSyncDamage) {}
} }