From 0d61406e33b88f6be1a41bf451b840b65f5364e6 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 13 Nov 2025 15:26:50 +0100 Subject: [PATCH 1/4] init Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 1 + build.gradle.kts | 1 + .../vanillaplus/VanillaPlusBootstrap.kt | 43 +++++++++++++++++++ .../enchantments/VeinMineEnchantment.kt | 27 ++++++++++++ .../interfaces/EnchantmentInterface.kt | 32 ++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt create mode 100644 src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt create mode 100644 src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 8a39c187d..5596fa788 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -38,6 +38,7 @@ unloadinv userdev vanillaplus + veinmine worldedit xodium xparser diff --git a/build.gradle.kts b/build.gradle.kts index 82b7e6027..9284e7051 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,6 +62,7 @@ paperPluginYaml { main.set(group.toString()) authors.add("Xodium") apiVersion.set(version) + bootstrapper.set("org.xodium.vanillaplus.VanillaPlusBootstrap") dependencies { server(name = "WorldEdit", load = PaperPluginYaml.Load.BEFORE, required = false, joinClasspath = true) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt new file mode 100644 index 000000000..d13f99728 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt @@ -0,0 +1,43 @@ +@file:Suppress("ktlint:standard:no-wildcard-imports") + +package org.xodium.vanillaplus + +import io.papermc.paper.plugin.bootstrap.BootstrapContext +import io.papermc.paper.plugin.bootstrap.PluginBootstrap +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents +import io.papermc.paper.registry.RegistryKey +import io.papermc.paper.registry.event.RegistryEvents +import io.papermc.paper.registry.keys.tags.EnchantmentTagKeys +import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys +import org.xodium.vanillaplus.enchantments.VeinMineEnchantment + +/** Main bootstrap class of the plugin. */ +@Suppress("UnstableApiUsage", "Unused") +internal class VanillaPlusBootstrap : PluginBootstrap { + companion object { + const val INSTANCE = "vanillaplus" + val VEINMINE = VeinMineEnchantment.key + val ENCHANTS = setOf(VEINMINE) + } + + override fun bootstrap(ctx: BootstrapContext) { + ctx.lifecycleManager.apply { + registerEventHandler( + RegistryEvents.ENCHANTMENT.compose().newHandler { event -> + event.getOrCreateTag(ItemTypeTagKeys.HARNESSES) + val enchantableMiningTag = event.getOrCreateTag(ItemTypeTagKeys.ENCHANTABLE_MINING) + event.registry().apply { + register(VEINMINE) { VeinMineEnchantment.init(it).supportedItems(enchantableMiningTag) } + } + }, + ) + registerEventHandler(LifecycleEvents.TAGS.postFlatten(RegistryKey.ENCHANTMENT)) { event -> + event.registrar().apply { + addToTag(EnchantmentTagKeys.TRADEABLE, ENCHANTS) + addToTag(EnchantmentTagKeys.NON_TREASURE, ENCHANTS) + addToTag(EnchantmentTagKeys.IN_ENCHANTING_TABLE, ENCHANTS) + } + } + } + } +} diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt new file mode 100644 index 000000000..5b88414cd --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt @@ -0,0 +1,27 @@ +package org.xodium.vanillaplus.enchantments + +import io.papermc.paper.registry.RegistryKey +import io.papermc.paper.registry.TypedKey +import io.papermc.paper.registry.data.EnchantmentRegistryEntry +import net.kyori.adventure.key.Key +import org.bukkit.enchantments.Enchantment +import org.bukkit.inventory.EquipmentSlotGroup +import org.xodium.vanillaplus.VanillaPlusBootstrap.Companion.INSTANCE +import org.xodium.vanillaplus.VanillaPlusBootstrap.Companion.VEINMINE +import org.xodium.vanillaplus.interfaces.EnchantmentInterface +import org.xodium.vanillaplus.utils.ExtUtils.mm + +@Suppress("UnstableApiUsage") +internal object VeinMineEnchantment : EnchantmentInterface { + override val key: TypedKey = TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(INSTANCE, "vein_mine")) + + override fun init(builder: EnchantmentRegistryEntry.Builder): EnchantmentRegistryEntry.Builder = + builder + .description(VEINMINE.value().replaceFirstChar { it.uppercase() }.mm()) + .anvilCost(7) + .maxLevel(1) + .weight(1) + .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(15, 0)) + .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(65, 0)) + .activeSlots(EquipmentSlotGroup.MAINHAND) +} diff --git a/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt b/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt new file mode 100644 index 000000000..e252fb96d --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt @@ -0,0 +1,32 @@ +package org.xodium.vanillaplus.interfaces + +import io.papermc.paper.registry.RegistryAccess +import io.papermc.paper.registry.RegistryKey +import io.papermc.paper.registry.TypedKey +import io.papermc.paper.registry.data.EnchantmentRegistryEntry +import org.bukkit.enchantments.Enchantment + +/** Represents a contract for enchantments within the system. */ +@Suppress("UnstableApiUsage") +internal interface EnchantmentInterface { + /** + * The unique typed key that identifies this enchantment in the registry. + * @see TypedKey + * @see RegistryKey.ENCHANTMENT + */ + val key: TypedKey + + /** + * Initializes the Drift enchantment. + * @param builder The builder used to define the enchantment properties. + * @return The builder for method chaining. + */ + fun init(builder: EnchantmentRegistryEntry.Builder): EnchantmentRegistryEntry.Builder + + /** + * Retrieves the enchantment from the registry. + * @return The [Enchantment] instance corresponding to the key. + * @throws NoSuchElementException if the enchantment is not found in the registry. + */ + fun get(): Enchantment = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).getOrThrow(key) +} From c384b79c93f9f9e4a91e03ae2dab025d53b45379 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 13 Nov 2025 15:28:17 +0100 Subject: [PATCH 2/4] setup Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlusBootstrap.kt | 9 ++++----- .../{VeinMineEnchantment.kt => ReplantEnchantment.kt} | 9 +++++---- 2 files changed, 9 insertions(+), 9 deletions(-) rename src/main/kotlin/org/xodium/vanillaplus/enchantments/{VeinMineEnchantment.kt => ReplantEnchantment.kt} (76%) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt index d13f99728..0d592b2b1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt @@ -9,25 +9,24 @@ import io.papermc.paper.registry.RegistryKey import io.papermc.paper.registry.event.RegistryEvents import io.papermc.paper.registry.keys.tags.EnchantmentTagKeys import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys -import org.xodium.vanillaplus.enchantments.VeinMineEnchantment +import org.xodium.vanillaplus.enchantments.ReplantEnchantment /** Main bootstrap class of the plugin. */ @Suppress("UnstableApiUsage", "Unused") internal class VanillaPlusBootstrap : PluginBootstrap { companion object { const val INSTANCE = "vanillaplus" - val VEINMINE = VeinMineEnchantment.key - val ENCHANTS = setOf(VEINMINE) + val REPLANT = ReplantEnchantment.key + val ENCHANTS = setOf(REPLANT) } override fun bootstrap(ctx: BootstrapContext) { ctx.lifecycleManager.apply { registerEventHandler( RegistryEvents.ENCHANTMENT.compose().newHandler { event -> - event.getOrCreateTag(ItemTypeTagKeys.HARNESSES) val enchantableMiningTag = event.getOrCreateTag(ItemTypeTagKeys.ENCHANTABLE_MINING) event.registry().apply { - register(VEINMINE) { VeinMineEnchantment.init(it).supportedItems(enchantableMiningTag) } + register(REPLANT) { ReplantEnchantment.init(it).supportedItems(enchantableMiningTag) } } }, ) diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt similarity index 76% rename from src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt rename to src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt index 5b88414cd..2a6f1f1b3 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt @@ -7,17 +7,18 @@ import net.kyori.adventure.key.Key import org.bukkit.enchantments.Enchantment import org.bukkit.inventory.EquipmentSlotGroup import org.xodium.vanillaplus.VanillaPlusBootstrap.Companion.INSTANCE -import org.xodium.vanillaplus.VanillaPlusBootstrap.Companion.VEINMINE +import org.xodium.vanillaplus.VanillaPlusBootstrap.Companion.REPLANT import org.xodium.vanillaplus.interfaces.EnchantmentInterface import org.xodium.vanillaplus.utils.ExtUtils.mm @Suppress("UnstableApiUsage") -internal object VeinMineEnchantment : EnchantmentInterface { - override val key: TypedKey = TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(INSTANCE, "vein_mine")) +internal object ReplantEnchantment : EnchantmentInterface { + override val key: TypedKey = TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(INSTANCE, "replant")) override fun init(builder: EnchantmentRegistryEntry.Builder): EnchantmentRegistryEntry.Builder = builder - .description(VEINMINE.value().replaceFirstChar { it.uppercase() }.mm()) + .description(REPLANT.value().replaceFirstChar { it.uppercase() }.mm()) + // TODO: adjust config values. .anvilCost(7) .maxLevel(1) .weight(1) From 596d54bf27f909e63ec0eef304a2d906911a7b7c Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 13 Nov 2025 15:36:18 +0100 Subject: [PATCH 3/4] Add automatic crop replanting on block break in PlayerModule Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 1 + .../vanillaplus/modules/PlayerModule.kt | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 5596fa788..1b9931fdd 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -1,6 +1,7 @@ + ageable appleboy armorposer birdflop diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index b110cf698..8d668a749 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -7,9 +7,12 @@ import io.papermc.paper.datacomponent.item.ItemLore import io.papermc.paper.datacomponent.item.ResolvableProfile import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.Material +import org.bukkit.block.Block +import org.bukkit.block.data.Ageable import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority +import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.InventoryClickEvent @@ -76,11 +79,15 @@ internal class PlayerModule( @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) fun on(event: PlayerJoinEvent) { if (!enabled()) return + val player = event.player + player.displayName(player.nickname?.mm()) if (config.i18n.playerJoinMsg.isEmpty()) return + event.joinMessage(null) + instance.server.onlinePlayers .filter { it.uniqueId != player.uniqueId } .forEach { @@ -95,13 +102,16 @@ internal class PlayerModule( @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) fun on(event: PlayerQuitEvent) { if (!enabled() || config.i18n.playerQuitMsg.isEmpty()) return + event.quitMessage(config.i18n.playerQuitMsg.mm(Placeholder.component("player", event.player.displayName()))) } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) fun on(event: PlayerDeathEvent) { if (!enabled()) return + val killer = event.entity.killer ?: return + if (Math.random() < config.skullDropChance) { event.entity.world.dropItemNaturally( event.entity.location, @@ -116,6 +126,7 @@ internal class PlayerModule( @EventHandler fun on(event: PlayerAdvancementDoneEvent) { if (!enabled() || config.i18n.playerAdvancementDoneMsg.isEmpty()) return + event.message( config.i18n.playerAdvancementDoneMsg.mm( Placeholder.component("player", event.player.displayName()), @@ -133,7 +144,9 @@ internal class PlayerModule( ) { return } + event.isCancelled = true + instance.server.scheduler.runTask( instance, Runnable { event.whoClicked.openInventory(event.whoClicked.enderChest) }, @@ -143,9 +156,40 @@ internal class PlayerModule( @EventHandler(ignoreCancelled = true) fun on(event: PlayerInteractEvent) { if (!enabled()) return + xpToBottle(event) } + @EventHandler + fun on(event: BlockBreakEvent) { + if (!enabled()) return + + replant(event.block) + } + + /** + * Automatically replants a crop block after it has been fully grown and harvested. + * @param block The block that was broken. + */ + private fun replant(block: Block) { + if (block.blockData !is Ageable) return + + val ageable = block.blockData as Ageable + + if (ageable.age < ageable.maximumAge) return + + instance.server.scheduler.runTaskLater( + instance, + Runnable { + val blockType = block.type + block.type = blockType + ageable.age = 0 + block.blockData = ageable + }, + 2, + ) + } + /** * Handles the interaction event where a player can convert their experience points into an experience bottle * if specific conditions are met. From 2065b601de794e1d9ea32c907ad2bd69e76bbe7c Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 13 Nov 2025 15:49:41 +0100 Subject: [PATCH 4/4] Implement replanting functionality with ReplantEnchantment in PlayerModule Signed-off-by: Illyrius --- .../xodium/vanillaplus/VanillaPlusBootstrap.kt | 4 ++-- .../enchantments/ReplantEnchantment.kt | 9 ++++----- .../xodium/vanillaplus/modules/PlayerModule.kt | 18 +++++++++++------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt index 0d592b2b1..af1454158 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt @@ -24,9 +24,9 @@ internal class VanillaPlusBootstrap : PluginBootstrap { ctx.lifecycleManager.apply { registerEventHandler( RegistryEvents.ENCHANTMENT.compose().newHandler { event -> - val enchantableMiningTag = event.getOrCreateTag(ItemTypeTagKeys.ENCHANTABLE_MINING) + val hoeTag = event.getOrCreateTag(ItemTypeTagKeys.HOES) event.registry().apply { - register(REPLANT) { ReplantEnchantment.init(it).supportedItems(enchantableMiningTag) } + register(REPLANT) { ReplantEnchantment.init(it).supportedItems(hoeTag) } } }, ) diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt index 2a6f1f1b3..d3d260871 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt @@ -18,11 +18,10 @@ internal object ReplantEnchantment : EnchantmentInterface { override fun init(builder: EnchantmentRegistryEntry.Builder): EnchantmentRegistryEntry.Builder = builder .description(REPLANT.value().replaceFirstChar { it.uppercase() }.mm()) - // TODO: adjust config values. - .anvilCost(7) + .anvilCost(8) .maxLevel(1) - .weight(1) - .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(15, 0)) - .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(65, 0)) + .weight(5) + .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 10)) + .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(8, 20)) .activeSlots(EquipmentSlotGroup.MAINHAND) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 8d668a749..b20500a5b 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -26,6 +26,7 @@ import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData +import org.xodium.vanillaplus.enchantments.ReplantEnchantment import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.pdcs.PlayerPDC.nickname import org.xodium.vanillaplus.utils.ExtUtils.mm @@ -164,27 +165,30 @@ internal class PlayerModule( fun on(event: BlockBreakEvent) { if (!enabled()) return - replant(event.block) + replant(event.block, event.player.inventory.itemInMainHand) } /** * Automatically replants a crop block after it has been fully grown and harvested. * @param block The block that was broken. + * @param tool The tool used to break the block. */ - private fun replant(block: Block) { - if (block.blockData !is Ageable) return - - val ageable = block.blockData as Ageable + private fun replant( + block: Block, + tool: ItemStack, + ) { + val ageable = block.blockData as? Ageable ?: return if (ageable.age < ageable.maximumAge) return + if (!tool.hasItemMeta() || !tool.itemMeta.hasEnchant(ReplantEnchantment.get())) return instance.server.scheduler.runTaskLater( instance, Runnable { val blockType = block.type + block.type = blockType - ageable.age = 0 - block.blockData = ageable + block.blockData = ageable.apply { age = 0 } }, 2, )