Compare commits

...
12 Commits
13 changed files with 2731 additions and 3575 deletions
+3 -3
View File
@@ -10,13 +10,13 @@ jobs:
build-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
uses: actions/setup-java@v6
with:
java-version: 21
java-version: '21'
distribution: 'temurin'
- name: Set short commit hash
run: fullCommit=${{ github.sha }} && echo "SHORT_SHA=${fullCommit:0:8}" >> $GITHUB_ENV
+4 -4
View File
@@ -16,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Build Astro page
uses: withastro/action@v4
uses: withastro/action@v6
with:
path: ./docs
node-version: '22'
node-version: '24'
deploy:
needs: build
runs-on: ubuntu-latest
@@ -31,4 +31,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
+1 -1
View File
@@ -11,7 +11,7 @@ export default defineConfig({
sidebar: [
{
label: 'Documentation',
autogenerate: { directory: 'documentation' },
items: [{ autogenerate: { "directory": "documentation" } }]
},
{
label: 'Configuration',
+2317 -3546
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -10,8 +10,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.34.3",
"astro": "^5.6.1",
"sharp": "^0.32.5"
"@astrojs/starlight": "^0.42.4",
"astro": "^7.2.10"
}
}
@@ -4,11 +4,41 @@ description: How to specify common input types in configuration
---
## Tags
There are 2 types of tags that can be used in the configuration:
- `minecraft:item_name` - For specific items, use namespaced vanilla item id. If namespace is not provided, `minecraft` is assumed.
- `#minecraft:item_tag_name` - For <a href="https://minecraft.wiki/w/Item_tag_(Java_Edition)" target="_blank">item tags</a>,
use namespaced tag id. If namespace is not provided, `minecraft` is assumed. Custom tags from other plugins and
datapacks are also supported.
### Items
#### Item reference
Example: `minecraft:stick`
For specific items, use namespaced vanilla item id. If namespace is not provided, `minecraft` is assumed.
#### Item tag
Example: `#minecraft:axes`
For <a href="https://minecraft.wiki/w/Item_tag_(Java_Edition)" target="_blank">item tags</a>, use namespaced tag id. If
namespace is not provided, `minecraft` is assumed. Custom tags from other plugins and datapacks are also supported.
### Blocks
#### Block reference
Example: `minecraft:dirt`
For specific items, use namespaced vanilla item id. If namespace is not provided, `minecraft` is assumed.
#### Block tag
Example: `#minecraft:planks`
For <a href="https://minecraft.wiki/w/Block_tag_(Java_Edition)" target="_blank">block tags</a>, use namespaced tag id. If
namespace is not provided, `minecraft` is assumed. Custom tags from other plugins and datapacks are also supported.
### Enchantments
#### Enchantment tag
Example: `#minecraft:in_enchanting_table`
For <a href="https://minecraft.wiki/w/Enchantment_tag_(Java_Edition)" target="_blank">enchantment tags</a>, use namespaced tag id. If
namespace is not provided, `minecraft` is assumed. Custom tags from other plugins and datapacks are also supported.
## Slot types
- `ANY` - Any slot.
@@ -18,8 +18,8 @@ Cost that will be added to the item when adding the enchantment to it in anvil.
- **Type**: `int`
Weight of the enchantment. Used if `canGetFromEnchantingTable` is set to true. Higher numbers mean it will come up more
frequently in the enchanting table.
Weight of the enchantment. Higher numbers mean it will come up more frequently in the enchanting table. Used if
`#minecraft:in_enchanting_table` is present in enchantment tags.
### minimumCost
@@ -29,7 +29,7 @@ Minimum cost that will be required for the enchantment to show up in the enchant
- **Type**: `int`
Base cost of the enchant.
Base cost of the enchantment.
#### perLevel
@@ -45,7 +45,7 @@ Maximum cost that will be required for the enchantment to show up in the enchant
- **Type**: `int`
Base cost of the enchant.
Base cost of the enchantment.
#### perLevel
@@ -55,23 +55,22 @@ Cost to add per level of the enchantment.
### enchantmentTags
- **Type**: `key[]`
- **Type**: [`Enchantment Tags`](/configuration/input-types/#enchantment-tag)
This is list of [tags](/configuration/input-types#tags) that the enchantment will be tagged with. This can be used to put the enchantment in enchanting table,
mark it as a curse, etc. <a href="https://minecraft.wiki/w/Enchantment_tag_(Java_Edition)" target="_blank">All vanilla</a>
and custom tags are supported.
This is list of tags that the enchantment will be tagged with. This can be used to put the enchantment in enchanting table,
mark it as a curse, etc.
### supportedItemTags
- **Type**: `key[]`
- **Type**: [`Item Tags`](/configuration/input-types/#items)
[Items and/or item tags](/configuration/input-types#tags) that the enchantment can be applied to.
Items and/or item tags that the enchantment can be applied to.
### activeSlots
- **Type**: `string[]`
- **Type**: [`Slot types`](/configuration/input-types/#slot-types)
[Slots](/configuration/input-types#slot-types) that the enchantment will be active in.
Inventory slots that the enchantment will be active in.
### enabled
@@ -96,9 +95,11 @@ of the enchantment does not support multiple levels.
## Telepathy
### onlyUserCanPickupItems
- **Type**: `boolean`
If set to true, items teleported by the enchant will only be able to be picked up by the player that broke the block.
If set to true, items teleported by the enchantment will only be able to be picked up by the player that broke the block.
## Executioner
@@ -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,169 @@
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 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
) {
this.anvilCost = anvilCost;
this.weight = weight;
this.minimumCost = minimumCost;
this.maximumCost = maximumCost;
this.maxLevel = maxLevel;
this.blocksPerLevel = blocksPerLevel;
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 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:terracotta",
"#minecraft:glazed_terracotta",
"#minecraft:concrete",
"#minecraft:stone_bricks",
"minecraft:end_stone",
"minecraft:cobblestone",
"minecraft:cobbled_deepslate"
)
))
),
EnchantioConfig.getInt(configurationSection, "maxLevel", 1),
EnchantioConfig.getInt(configurationSection, "blocksPerLevel", 1)
);
if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) {
ENCHANTS.put(TunnellingEnchant.KEY, tunnellingEnchant);
}
return tunnellingEnchant;
}
}
@@ -0,0 +1,122 @@
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.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.HashSet;
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 (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) {
if (player.getInventory().getItemInMainHand().isEmpty()) {
event.setCancelled(true);
blockBreakSkips.removeAll(blocksToBreak);
break;
}
event.getPlayer().breakBlock(b);
blockBreakSkips.remove(b);
}
}
private Set<Block> getSquare(Block block, BlockFace face, int range) {
switch (face) {
case DOWN, UP -> {
return getBlocksToTunnel(block.getLocation(), range, 0, range);
}
case EAST, WEST -> {
return getBlocksToTunnel(block.getLocation(), 0, range, range);
}
case NORTH, SOUTH -> {
return getBlocksToTunnel(block.getLocation(), range, range, 0);
}
}
return new HashSet<>();
}
/**
* 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 Set<Block> getBlocksToTunnel(Location base, int changeX, int changeY, int changeZ) {
Set<Block> blocks = new HashSet<>();
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;
}
}