diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 8a39c187d..1b9931fdd 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -1,6 +1,7 @@ + ageable appleboy armorposer birdflop @@ -38,6 +39,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..af1454158 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlusBootstrap.kt @@ -0,0 +1,42 @@ +@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.ReplantEnchantment + +/** Main bootstrap class of the plugin. */ +@Suppress("UnstableApiUsage", "Unused") +internal class VanillaPlusBootstrap : PluginBootstrap { + companion object { + const val INSTANCE = "vanillaplus" + val REPLANT = ReplantEnchantment.key + val ENCHANTS = setOf(REPLANT) + } + + override fun bootstrap(ctx: BootstrapContext) { + ctx.lifecycleManager.apply { + registerEventHandler( + RegistryEvents.ENCHANTMENT.compose().newHandler { event -> + val hoeTag = event.getOrCreateTag(ItemTypeTagKeys.HOES) + event.registry().apply { + register(REPLANT) { ReplantEnchantment.init(it).supportedItems(hoeTag) } + } + }, + ) + 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/ReplantEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt new file mode 100644 index 000000000..d3d260871 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.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.REPLANT +import org.xodium.vanillaplus.interfaces.EnchantmentInterface +import org.xodium.vanillaplus.utils.ExtUtils.mm + +@Suppress("UnstableApiUsage") +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(REPLANT.value().replaceFirstChar { it.uppercase() }.mm()) + .anvilCost(8) + .maxLevel(1) + .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/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) +} diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index b110cf698..b20500a5b 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 @@ -23,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 @@ -76,11 +80,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 +103,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 +127,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 +145,9 @@ internal class PlayerModule( ) { return } + event.isCancelled = true + instance.server.scheduler.runTask( instance, Runnable { event.whoClicked.openInventory(event.whoClicked.enderChest) }, @@ -143,9 +157,43 @@ 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, 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, + 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 + block.blockData = ageable.apply { age = 0 } + }, + 2, + ) + } + /** * Handles the interaction event where a player can convert their experience points into an experience bottle * if specific conditions are met.