Compare commits

...
10 Commits
5 changed files with 128 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.
## 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
### 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
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
### Curse of Panic
+1 -1
View File
@@ -15,7 +15,7 @@
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>1.15.2</revision>
<revision>1.16.1</revision>
</properties>
<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");
private final int anvilCost, weight, maxLevel, blocksPerLevel;
private final boolean visualizeBreaking;
private final EnchantmentRegistryEntry.EnchantmentCost minimumCost;
private final EnchantmentRegistryEntry.EnchantmentCost maximumCost;
private final Set<TagEntry<ItemType>> supportedItemTags = new HashSet<>();
@@ -40,7 +41,8 @@ public class TunnellingEnchant implements EnchantioEnchant {
Collection<TagEntry<ItemType>> supportedItemTags,
Map<Key, Set<TagEntry<BlockType>>> affectedBlockTags,
int maxLevel,
int blocksPerLevel
int blocksPerLevel,
boolean visualizeBreaking
) {
this.anvilCost = anvilCost;
this.weight = weight;
@@ -48,6 +50,7 @@ public class TunnellingEnchant implements EnchantioEnchant {
this.maximumCost = maximumCost;
this.maxLevel = maxLevel;
this.blocksPerLevel = blocksPerLevel;
this.visualizeBreaking = visualizeBreaking;
this.supportedItemTags.addAll(supportedItemTags);
this.enchantTagKeys.addAll(enchantTagKeys);
this.affectedBlockTags.putAll(affectedBlockTags);
@@ -112,6 +115,10 @@ public class TunnellingEnchant implements EnchantioEnchant {
return blocksPerLevel;
}
public boolean shouldVisualizeBreaking() {
return visualizeBreaking;
}
public static TunnellingEnchant create(ConfigurationSection configurationSection) {
TunnellingEnchant tunnellingEnchant = new TunnellingEnchant(
EnchantioConfig.getInt(configurationSection, "anvilCost", 1),
@@ -156,7 +163,8 @@ public class TunnellingEnchant implements EnchantioEnchant {
))
),
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)) {
@@ -3,8 +3,8 @@ 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;
@@ -15,60 +15,111 @@ 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.event.block.BlockDamageAbortEvent;
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.persistence.PersistentDataType;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings("UnstableApiUsage")
public class TunnellingListener implements Listener {
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 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)
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;
if (player.isSneaking()) {
updateBlockBreakProgress(player, data, 0);
return;
}
long elapsed = System.currentTimeMillis() - data.startedBreakingAt();
long elapsedTicks = elapsed / 50;
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 (event.getHand() != EquipmentSlot.HAND) return;
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
Player player = event.getPlayer();
if (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());
ItemStack item = player.getInventory().getItemInMainHand();
if (item.isEmpty()) return;
int enchantLevel = item.getEnchantmentLevel(tunnelling);
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, System.currentTimeMillis(), 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)
public void onTunnelBlockBreak(BlockBreakEvent event) {
if (tunnelling == null) return;
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 (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);
if (data == null) return;
Set<Block> blocksToBreak = getSquare(block, data.blockFace, tunnellingEnchant.getBlocksPerLevel() * enchantLevel);
blockBreakSkips.addAll(blocksToBreak);
for (Block b : blocksToBreak) {
if (player.getInventory().getItemInMainHand().isEmpty()) {
@@ -81,6 +132,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) {
switch (face) {
case DOWN, UP -> {
@@ -119,4 +178,6 @@ public class TunnellingListener implements Listener {
return blocks;
}
private record BlockBreakData(Block block, long startedBreakingAt, BlockFace blockFace, Set<Block> blocksToSyncDamage) {}
}