From d9d38b6c00e03351403e4aabdbd30c2ca221c8fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:49:36 +0000 Subject: [PATCH 01/64] [ci-skip] Update gradle/actions digest to 4f91d42 (#341) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index fc76e5950..6dfbb9375 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@63b23c47ec369cc3851ad527daec269a2f949651 + uses: gradle/actions/setup-gradle@4f91d429570f31ab67924719ed3669e5c820e072 - id: build-gradle name: Build with Gradle From 487c67ea8889b59cf96016e9c1ae5800aa28f88b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 02:04:11 +0000 Subject: [PATCH 02/64] [ci-skip] Update actions/upload-artifact action to v6 (#342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index 6dfbb9375..1f6cd2405 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -38,6 +38,6 @@ jobs: - id: upload-artifact name: Upload Build Artifact if: ${{ success() }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: path: build/libs/*.jar From 457ff25cea1f2e4bc18034a4bc1f11eca99e0b30 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Sat, 13 Dec 2025 18:37:39 +0100 Subject: [PATCH 03/64] Refactor code for improved readability and add isEnabled property to ModuleInterface Signed-off-by: Illyrius --- .../interfaces/EnchantmentInterface.kt | 3 +- .../vanillaplus/interfaces/ModuleInterface.kt | 31 +++++++++++++++---- .../vanillaplus/managers/ConfigManager.kt | 4 ++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt b/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt index df4cd5a76..62d100f67 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/interfaces/EnchantmentInterface.kt @@ -17,8 +17,7 @@ internal interface EnchantmentInterface { * Retrieves the configuration data associated with the module. * @return A [ConfigData] object representing the configuration for the module. */ - val config: ConfigData - get() = configData + val config: ConfigData get() = configData /** * The unique typed key identifies this enchantment in the registry. diff --git a/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt b/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt index 26116b617..7acf6f14f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt @@ -7,6 +7,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.configData import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.data.ConfigData +import kotlin.reflect.full.memberProperties import kotlin.time.measureTime /** Represents a contract for a module within the system. */ @@ -15,26 +16,44 @@ internal interface ModuleInterface : Listener { * Retrieves the configuration data associated with the module. * @return A [ConfigData] object representing the configuration for the module. */ - val config: ConfigData - get() = configData + val config: ConfigData get() = configData + + /** + * Determines if this module is enabled. + * @return True if the module is enabled, false otherwise. + */ + val isEnabled: Boolean + get() = + configData::class + .memberProperties + .firstOrNull { property -> + property.name == (this::class.simpleName?.replaceFirstChar { it.lowercase() } ?: return true) + }?.call(configData) + ?.let { moduleConfig -> + moduleConfig::class + .memberProperties + .firstOrNull { prop -> prop.name == "enabled" } + ?.call(moduleConfig) as? Boolean + } + ?: true /** * Retrieves a list of command data associated with the module. * @return A [Collection] of [CommandData] objects representing the commands for the module. */ - val cmds: Collection - get() = emptyList() + val cmds: Collection get() = emptyList() /** * Retrieves a list of permissions associated with this module. * @return A [List] of [Permission] objects representing the permissions for this module. */ - val perms: List - get() = emptyList() + val perms: List get() = emptyList() /** Registers this feature as an event listener with the server. */ @Suppress("UnstableApiUsage") fun register() { + if (!isEnabled) return + instance.logger.info( "Registering: ${this::class.simpleName} | Took ${ measureTime { diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index dbb03a4c4..d4ee6ff12 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -38,7 +38,9 @@ internal object ConfigManager { Commands .literal("reload") .executesCatching { - if (it.source.sender !is Player) instance.logger.warning("Command can only be executed by a Player!") + if (it.source.sender !is Player) { + instance.logger.warning("Command can only be executed by a Player!") + } configData = load() it.source.sender.sendMessage("${instance.prefix} configuration reloaded!".mm()) }, From 61833780458575b6c10f0ac12176100bff793b7c Mon Sep 17 00:00:00 2001 From: Illyrius Date: Sat, 13 Dec 2025 18:45:33 +0100 Subject: [PATCH 04/64] Refactor module registration for improved readability and maintainability Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 90f6ab9ce..ea8de8a73 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -43,26 +43,30 @@ internal class VanillaPlus : JavaPlugin() { configData = ConfigManager.load() - RottenFleshRecipe.register() - TorchArrowRecipe.register() - WoodLogRecipe.register() + listOf( + RottenFleshRecipe, + TorchArrowRecipe, + WoodLogRecipe, + ).forEach { module -> module.register() } - BooksModule.register() - ChatModule.register() - DimensionsModule.register() - EntityModule.register() - InvModule.register() - LocatorModule.register() - MotdModule.register() - OpenableModule.register() - PetModule.register() - PlayerModule.register() - ScoreBoardModule.register() - SignModule.register() - SitModule.register() - TabListModule.register() - ArrowModule.register() - if (WorldEditHook.get()) TreesModule.register() + listOfNotNull( + ArrowModule, + BooksModule, + ChatModule, + DimensionsModule, + EntityModule, + InvModule, + LocatorModule, + MotdModule, + OpenableModule, + PetModule, + PlayerModule, + ScoreBoardModule, + SignModule, + SitModule, + TabListModule, + if (WorldEditHook.get()) TreesModule else null, + ).forEach { module -> module.register() } } /** From 1a70dc573a8cc591feca8ac585b26c24951fa0a3 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Sat, 13 Dec 2025 18:59:12 +0100 Subject: [PATCH 05/64] Refactor configuration loading to support custom serializers and default values Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 2 +- .../vanillaplus/managers/ConfigManager.kt | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index ea8de8a73..158def150 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -41,7 +41,7 @@ internal class VanillaPlus : JavaPlugin() { } instance.server.pluginManager.addPermission(ConfigManager.reloadPermission) - configData = ConfigManager.load() + configData = ConfigManager.load("config.json", ConfigData(), ConfigData.serializer()) listOf( RottenFleshRecipe, diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index d4ee6ff12..0aabc2d72 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -4,6 +4,7 @@ package org.xodium.vanillaplus.managers import io.papermc.paper.command.brigadier.Commands import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import org.bukkit.entity.Player import org.bukkit.permissions.Permission @@ -41,7 +42,7 @@ internal object ConfigManager { if (it.source.sender !is Player) { instance.logger.warning("Command can only be executed by a Player!") } - configData = load() + configData = load("config.json", ConfigData(), ConfigData.serializer()) it.source.sender.sendMessage("${instance.prefix} configuration reloaded!".mm()) }, ), @@ -59,18 +60,24 @@ internal object ConfigManager { /** * Loads or creates the configuration file. * @param fileName The name of the configuration file. + * @param config The default configuration data. + * @param serializer The serializer for the configuration data. * @return The loaded configuration data. */ - fun load(fileName: String = "config.json"): ConfigData { + inline fun load( + fileName: String, + config: T, + serializer: KSerializer, + ): T { val file = File(instance.dataFolder, fileName) if (!instance.dataFolder.exists()) instance.dataFolder.mkdirs() - val config = getOrCreateConfig(file) + val config = getOrCreateConfig(file, config, serializer) instance.logger.info( "${if (file.exists()) "Loaded configuration from $fileName" else "Created default $fileName"} | Took ${ - measureTime { file.writeText(json.encodeToString(ConfigData.serializer(), config)) }.inWholeMilliseconds + measureTime { file.writeText(json.encodeToString(serializer, config)) }.inWholeMilliseconds }ms", ) @@ -80,8 +87,12 @@ internal object ConfigManager { /** * Gets the existing configuration or creates a default one. * @param file The configuration file. + * @param config The default configuration data. * @return The configuration data. */ - private fun getOrCreateConfig(file: File): ConfigData = - if (file.exists()) json.decodeFromString(ConfigData.serializer(), file.readText()) else ConfigData() + private fun getOrCreateConfig( + file: File, + config: T, + serializer: KSerializer, + ): T = if (file.exists()) json.decodeFromString(serializer, file.readText()) else config } From 7e5ef7699d6e56261fdee582cf71a00944603bf2 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Sat, 13 Dec 2025 19:09:08 +0100 Subject: [PATCH 06/64] Refactor configuration loading to eliminate serializer parameter and streamline config handling Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 2 +- .../vanillaplus/managers/ConfigManager.kt | 23 ++++--------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 158def150..4b8ff55b5 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -41,7 +41,7 @@ internal class VanillaPlus : JavaPlugin() { } instance.server.pluginManager.addPermission(ConfigManager.reloadPermission) - configData = ConfigManager.load("config.json", ConfigData(), ConfigData.serializer()) + configData = ConfigManager.load("config.json", ConfigData()) listOf( RottenFleshRecipe, diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index 0aabc2d72..9e7872e44 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -4,7 +4,6 @@ package org.xodium.vanillaplus.managers import io.papermc.paper.command.brigadier.Commands import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import org.bukkit.entity.Player import org.bukkit.permissions.Permission @@ -42,7 +41,7 @@ internal object ConfigManager { if (it.source.sender !is Player) { instance.logger.warning("Command can only be executed by a Player!") } - configData = load("config.json", ConfigData(), ConfigData.serializer()) + configData = load("config.json", ConfigData()) it.source.sender.sendMessage("${instance.prefix} configuration reloaded!".mm()) }, ), @@ -61,38 +60,24 @@ internal object ConfigManager { * Loads or creates the configuration file. * @param fileName The name of the configuration file. * @param config The default configuration data. - * @param serializer The serializer for the configuration data. * @return The loaded configuration data. */ inline fun load( fileName: String, config: T, - serializer: KSerializer, ): T { val file = File(instance.dataFolder, fileName) if (!instance.dataFolder.exists()) instance.dataFolder.mkdirs() - val config = getOrCreateConfig(file, config, serializer) + val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else config instance.logger.info( "${if (file.exists()) "Loaded configuration from $fileName" else "Created default $fileName"} | Took ${ - measureTime { file.writeText(json.encodeToString(serializer, config)) }.inWholeMilliseconds + measureTime { file.writeText(json.encodeToString(loadedConfig)) }.inWholeMilliseconds }ms", ) - return config + return loadedConfig } - - /** - * Gets the existing configuration or creates a default one. - * @param file The configuration file. - * @param config The default configuration data. - * @return The configuration data. - */ - private fun getOrCreateConfig( - file: File, - config: T, - serializer: KSerializer, - ): T = if (file.exists()) json.decodeFromString(serializer, file.readText()) else config } From 1615786fbff6ace1f873af513fece44c6501194f Mon Sep 17 00:00:00 2001 From: Illyrius Date: Sat, 13 Dec 2025 19:12:41 +0100 Subject: [PATCH 07/64] Refactor configuration loading to use extension function for improved clarity Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt | 3 ++- .../org/xodium/vanillaplus/managers/ConfigManager.kt | 10 +++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 4b8ff55b5..15bd7bc9e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -7,6 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.hooks.WorldEditHook import org.xodium.vanillaplus.managers.ConfigManager +import org.xodium.vanillaplus.managers.ConfigManager.load import org.xodium.vanillaplus.modules.* import org.xodium.vanillaplus.recipes.RottenFleshRecipe import org.xodium.vanillaplus.recipes.TorchArrowRecipe @@ -41,7 +42,7 @@ internal class VanillaPlus : JavaPlugin() { } instance.server.pluginManager.addPermission(ConfigManager.reloadPermission) - configData = ConfigManager.load("config.json", ConfigData()) + configData = ConfigData().load("config.json") listOf( RottenFleshRecipe, diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index 9e7872e44..4752e7b44 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -41,7 +41,7 @@ internal object ConfigManager { if (it.source.sender !is Player) { instance.logger.warning("Command can only be executed by a Player!") } - configData = load("config.json", ConfigData()) + configData = ConfigData().load("config.json") it.source.sender.sendMessage("${instance.prefix} configuration reloaded!".mm()) }, ), @@ -59,18 +59,14 @@ internal object ConfigManager { /** * Loads or creates the configuration file. * @param fileName The name of the configuration file. - * @param config The default configuration data. * @return The loaded configuration data. */ - inline fun load( - fileName: String, - config: T, - ): T { + inline fun T.load(fileName: String): T { val file = File(instance.dataFolder, fileName) if (!instance.dataFolder.exists()) instance.dataFolder.mkdirs() - val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else config + val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else this instance.logger.info( "${if (file.exists()) "Loaded configuration from $fileName" else "Created default $fileName"} | Took ${ From f7de2ad64649ea94b57a344bffa2e1c3dd107679 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:03:22 +0000 Subject: [PATCH 08/64] [ci-skip] Update gradle/actions digest to b6208dc (#343) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index 1f6cd2405..650349449 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@4f91d429570f31ab67924719ed3669e5c820e072 + uses: gradle/actions/setup-gradle@b6208dca2e6d55209f73df1d80993f06c285a018 - id: build-gradle name: Build with Gradle From 2fd97d6463e5057f185d13968557caf6b9e6c392 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:38:53 +0100 Subject: [PATCH 09/64] [ci-skip] Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.0 (#345) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6ac43961b..9920e2cb0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("java") id("idea") kotlin("jvm") version "2.2.21" - kotlin("plugin.serialization") version "2.2.21" + kotlin("plugin.serialization") version "2.3.0" id("com.gradleup.shadow") version "9.3.0" id("xyz.jpenilla.run-paper") version "3.0.2" id("xyz.jpenilla.resource-factory-paper-convention") version "1.3.1" From d11243aeb09383bd74e67e1abca66712848ea73f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:39:38 +0100 Subject: [PATCH 10/64] [ci-skip] Update plugin org.jetbrains.kotlin.jvm to v2.3.0 (#344) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9920e2cb0..70ee405a5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import xyz.jpenilla.runtask.task.AbstractRun plugins { id("java") id("idea") - kotlin("jvm") version "2.2.21" + kotlin("jvm") version "2.3.0" kotlin("plugin.serialization") version "2.3.0" id("com.gradleup.shadow") version "9.3.0" id("xyz.jpenilla.run-paper") version "3.0.2" From 70f2808717d6ca1d20b65716d2a6292ca6acc8d4 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:32:19 +0100 Subject: [PATCH 11/64] remove inv unload, as since the copper update we can use vanilla copper golems for inventory unloading and sorting. (#346) Signed-off-by: Illyrius --- .idea/kotlinc.xml | 3 +- .../xodium/vanillaplus/modules/InvModule.kt | 66 ---------------- .../org/xodium/vanillaplus/utils/ExtUtils.kt | 3 +- .../org/xodium/vanillaplus/utils/InvUtils.kt | 76 ------------------- 4 files changed, 4 insertions(+), 144 deletions(-) delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/utils/InvUtils.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 8ad8c8610..739bc3658 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,7 @@ - \ No newline at end of file diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt index cd68d02ca..3e0983dfb 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt @@ -7,7 +7,6 @@ import com.mojang.brigadier.context.CommandContext import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import kotlinx.serialization.Serializable -import net.kyori.adventure.sound.Sound import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.Color import org.bukkit.Material @@ -18,13 +17,11 @@ 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.data.SoundData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.BlockUtils.center import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.ExtUtils.mm -import org.xodium.vanillaplus.utils.InvUtils import org.xodium.vanillaplus.utils.PlayerUtils import org.xodium.vanillaplus.utils.ScheduleUtils import java.util.concurrent.CompletableFuture @@ -51,14 +48,6 @@ internal object InvModule : ModuleInterface { "Search nearby chests for specific items", listOf("search", "searchinv", "invs"), ), - CommandData( - Commands - .literal("invunload") - .requires { it.sender.hasPermission(perms[1]) } - .playerExecuted { player, _ -> unload(player) }, - "Unload your inventory into nearby chests", - listOf("unload", "unloadinv", "invu"), - ), ) override val perms = @@ -68,11 +57,6 @@ internal object InvModule : ModuleInterface { "Allows use of the invsearch command", PermissionDefault.TRUE, ), - Permission( - "${instance.javaClass.simpleName}.invunload".lowercase(), - "Allows use of the invunload command", - PermissionDefault.TRUE, - ), ) /** @@ -150,57 +134,9 @@ internal object InvModule : ModuleInterface { } } - /** - * Unloads items from the player's inventory into nearby chests. - * @param player The player whose inventory is to be unloaded. - */ - private fun unload(player: Player) { - val foundContainers = mutableListOf() - - for (container in PlayerUtils.getContainersAroundPlayer(player)) { - val transferred = - InvUtils.transferItems( - source = player.inventory, - destination = container.inventory, - startSlot = 9, - endSlot = 35, - onlyMatching = true, - enchantmentChecker = { item1, item2 -> item1.enchantments == item2.enchantments }, - ) - - if (transferred) foundContainers.add(container.block) - } - - if (foundContainers.isEmpty()) { - return player.sendActionBar( - config.invModule.i18n.noItemsUnloaded - .mm(), - ) - } - - player.sendActionBar( - config.invModule.i18n.inventoryUnloaded - .mm(), - ) - player.playSound(config.invModule.soundOnUnload.toSound(), Sound.Emitter.self()) - - ScheduleUtils.schedule(duration = 60L) { - foundContainers.forEach { container -> - Particle.DUST - .builder() - .location(container.center) - .count(10) - .data(Particle.DustOptions(Color.LIME, 5.0f)) - .receivers(player) - .spawn() - } - } - } - @Serializable data class Config( var enabled: Boolean = true, - var soundOnUnload: SoundData = SoundData("entity.player.levelup", Sound.Source.PLAYER), var i18n: I18n = I18n(), ) { @Serializable @@ -213,8 +149,6 @@ internal object InvModule : ModuleInterface { "", var foundItemsInChests: String = "Found in container(s), follow trail(s)", - var noItemsUnloaded: String = "No items were unloaded", - var inventoryUnloaded: String = "Inventory unloaded", ) } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt index 7427acbc9..39106a067 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt @@ -139,7 +139,8 @@ internal object ExtUtils { } /** Extension function to convert snake_case to Proper Case with spaces. */ - fun String.snakeToProperCase(): String = split('_').joinToString(" ") { word -> word.replaceFirstChar { it.uppercase() } } + fun String.snakeToProperCase(): String = + split('_').joinToString(" ") { word -> word.replaceFirstChar { it.uppercase() } } /** Extension function specifically for enchantment keys */ fun TypedKey.displayName(): Component = value().snakeToProperCase().mm() diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/InvUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/InvUtils.kt deleted file mode 100644 index 0d1c34b40..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/InvUtils.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.xodium.vanillaplus.utils - -import org.bukkit.Tag -import org.bukkit.block.ShulkerBox -import org.bukkit.inventory.Inventory -import org.bukkit.inventory.ItemStack - -/** Inventory utilities. */ -internal object InvUtils { - /** - * Transfer items from source to destination inventory. - * @param source The source inventory. - * @param destination The destination inventory. - * @param startSlot The starting slot in source inventory. - * @param endSlot The ending slot in source inventory. - * @param onlyMatching If true, only transfer items that already exist in the destination. - * @param enchantmentChecker Function to check if enchantments match. - * @return Pair - */ - fun transferItems( - source: Inventory, - destination: Inventory, - startSlot: Int = 9, - endSlot: Int = 35, - onlyMatching: Boolean = false, - enchantmentChecker: (ItemStack, ItemStack) -> Boolean = { _, _ -> true }, - ): Boolean { - var moved = false - - for (i in startSlot..endSlot) { - val item = source.getItem(i) ?: continue - - if (!isValidTransfer(item, destination)) continue - if (onlyMatching && !containsMatchingItem(destination, item, enchantmentChecker)) continue - - val leftovers = destination.addItem(item) - val movedAmount = item.amount - leftovers.values.sumOf { it.amount } - - if (movedAmount > 0) { - moved = true - source.clear(i) - leftovers.values.firstOrNull()?.let { source.setItem(i, it) } - } - } - - return moved - } - - /** - * Check if transferring an item would be valid (not putting shulker in shulker, etc.) - * @param item The item to transfer. - * @param destination The destination inventory. - * @return True if the transfer is valid, false otherwise. - */ - private fun isValidTransfer( - item: ItemStack, - destination: Inventory, - ): Boolean = !(Tag.SHULKER_BOXES.isTagged(item.type) && destination.holder is ShulkerBox) - - /** - * Check if inventory contains an item with matching type and enchantments. - * @param inventory The inventory to check. - * @param item The item to match. - * @param enchantmentChecker Function to check enchantment compatibility. - * @return True if a matching item is found. - */ - private fun containsMatchingItem( - inventory: Inventory, - item: ItemStack, - enchantmentChecker: (ItemStack, ItemStack) -> Boolean, - ): Boolean = - inventory.contents - .asSequence() - .filterNotNull() - .any { it.type == item.type && enchantmentChecker(item, it) } -} From f48d83ab0b52f4a9f5f426eef6048656094cb237 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:03:39 +0000 Subject: [PATCH 12/64] [ci-skip] Update gradle/actions digest to 030fdb2 (#347) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index 650349449..c63ac14d7 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@b6208dca2e6d55209f73df1d80993f06c285a018 + uses: gradle/actions/setup-gradle@030fdb2d3d26e7c77770bfe796534fcd841c69dc - id: build-gradle name: Build with Gradle From 0bfee588b8711c34644a55c960d26350005a7427 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 18 Dec 2025 18:00:38 +0100 Subject: [PATCH 13/64] Refactor chat module strings for improved readability and add Qodana configuration Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 3 +++ .../kotlin/org/xodium/vanillaplus/modules/ChatModule.kt | 6 ++++-- .../kotlin/org/xodium/vanillaplus/recipes/WoodLogRecipe.kt | 2 +- .../kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 2fa839b98..d8376ca9c 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -6,6 +6,7 @@ armorposer birdflop cmds + codeql coord decentholograms decentsoftware @@ -19,6 +20,7 @@ glorp gradleup griefing + illyrius intellectualsites invs invsearch @@ -39,6 +41,7 @@ signedit spellbite tellraw + temurin triumphteam unloadinv userdev diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 2813a9e45..319b1c5cd 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -245,10 +245,12 @@ internal object ChatModule : ModuleInterface { data class I18n( var clickMe: String = "Click me!", var clickToWhisper: String = "Click to Whisper", - var playerIsNotOnline: String = "${instance.prefix} Player is not Online!", + var playerIsNotOnline: String = + "${instance.prefix} Player is not Online!", var deleteMessage: String = "Click to delete your message", var clickToClipboard: String = "Click to copy position to clipboard", - var playerSetSpawn: String = "", + var playerSetSpawn: String = + "", ) } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/recipes/WoodLogRecipe.kt b/src/main/kotlin/org/xodium/vanillaplus/recipes/WoodLogRecipe.kt index 4615ff6f6..491fb7138 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/recipes/WoodLogRecipe.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/recipes/WoodLogRecipe.kt @@ -42,7 +42,7 @@ internal object WoodLogRecipe : RecipeInterface { .map { (wood, log) -> ShapelessRecipe( NamespacedKey(instance, "${wood.key.key}_to_${log.key.key}"), - ItemStack.of(log), + ItemStack.of(log, 4), ).apply { addIngredient(wood) } }.toSet() } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt index 4a3d4fed3..9522af6ce 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt @@ -9,6 +9,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.utils.ExtUtils.mm import org.xodium.vanillaplus.utils.ExtUtils.prefix +/** Utility functions for command handling. */ internal object CommandUtils { /** * Registers a command execution handler with an automatic try/catch handling. From 9eea95847c5ca632787d23e71a898a5ca8f32b2d Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 18 Dec 2025 18:20:16 +0100 Subject: [PATCH 14/64] Refactor chat module strings for improved clarity and consistency Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/ChatModule.kt | 21 +++--------- .../org/xodium/vanillaplus/utils/ExtUtils.kt | 33 ------------------- 2 files changed, 4 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 319b1c5cd..6b61ec04a 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -21,9 +21,6 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.ExtUtils.clickOpenUrl -import org.xodium.vanillaplus.utils.ExtUtils.clickRunCmd -import org.xodium.vanillaplus.utils.ExtUtils.clickSuggestCmd import org.xodium.vanillaplus.utils.ExtUtils.face import org.xodium.vanillaplus.utils.ExtUtils.mm import org.xodium.vanillaplus.utils.ExtUtils.prefix @@ -216,21 +213,11 @@ internal object ChatModule : ModuleInterface { "]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[", "⯈", "⯈", - "Welcome " - .clickSuggestCmd( - "/nickname", - "Set your nickname!", - ), + "Welcome Set your nickname!'>", "⯈", - "Check out:", - "".clickRunCmd( - "/rules", - "View the server /rules", - ), - "".clickOpenUrl( - "https://illyria.fandom.com", - "Visit the wiki!", - ), + "Check out:", + " /rules", + " /wiki", "⯈", "]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[", ), diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt index 39106a067..2bd2888a2 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt @@ -57,39 +57,6 @@ internal object ExtUtils { @JvmName("mmStringIterable") fun Iterable.mm(vararg resolvers: TagResolver): List = map { it.mm(*resolvers) } - /** - * Performs a command from a [String]. - * @param cmd The command to perform. - * @param hover Optional hover text for the command. Defaults to "Click me!". - * @return The formatted [String] with the command. - */ - fun String.clickRunCmd( - cmd: String, - hover: String? = "Click me!", // FIX - ): String = "$this" - - /** - * Suggests a command from a [String]. - * @param cmd The command to suggest. - * @param hover Optional hover text for the command. Defaults to "Click me!". - * @return The formatted [String] with the suggested command. - */ - fun String.clickSuggestCmd( - cmd: String, - hover: String? = "Click me!", // FIX - ): String = "$this" - - /** - * Opens a URL from a [String]. - * @param url The URL to open. - * @param hover Optional hover text for the URL. Defaults to "Click me!". - * @return The formatted [String] with the URL. - */ - fun String.clickOpenUrl( - url: String, - hover: String? = "Click me!", // FIX - ): String = "$this" - /** * Retrieves the player's face as a string. * @param size The size of the face in pixels (default is 8). From 64a7dc0c055bfdfa898f3ada690567921eb0a4ce Mon Sep 17 00:00:00 2001 From: Illyrius Date: Fri, 19 Dec 2025 14:02:46 +0100 Subject: [PATCH 15/64] oops Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 6b61ec04a..7d0b7e70e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -217,7 +217,7 @@ internal object ChatModule : ModuleInterface { "⯈", "Check out:", " /rules", - " /wiki", + " wiki", "⯈", "]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[", ), From 37b7d398e05c64743304e6b0118cae098ff9bed2 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:41:07 +0100 Subject: [PATCH 16/64] refactor/LocatorModule/useWaypointAPI (#349) * init Signed-off-by: Illyrius * Refactor locator functionality to simplify color handling and improve player feedback Signed-off-by: Illyrius --------- Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/ChatModule.kt | 2 +- .../vanillaplus/modules/LocatorModule.kt | 50 ++++--------------- 2 files changed, 12 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 7d0b7e70e..5bc3a6079 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -213,7 +213,7 @@ internal object ChatModule : ModuleInterface { "]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[=]|[", "⯈", "⯈", - "Welcome Set your nickname!'>", + "Welcome Set your nickname!'> Change your locator color!'>", "⯈", "Check out:", " /rules", diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt index a7f6b911c..6594d1a52 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt @@ -5,8 +5,10 @@ package org.xodium.vanillaplus.modules import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.command.brigadier.argument.ArgumentTypes import kotlinx.serialization.Serializable +import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor +import org.bukkit.Color import org.bukkit.entity.Player import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault @@ -14,13 +16,9 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import java.util.* -import java.util.concurrent.CompletableFuture /** Represents a module handling locator mechanics within the system. */ internal object LocatorModule : ModuleInterface { - private val colors = NamedTextColor.NAMES.keys().map { it.toString() } + listOf("", "reset") - override val cmds = listOf( CommandData( @@ -30,24 +28,19 @@ internal object LocatorModule : ModuleInterface { .then( Commands .argument("color", ArgumentTypes.namedColor()) - .suggests { _, builder -> - colors - .filter { it.startsWith(builder.remaining.lowercase()) } - .forEach(builder::suggest) - CompletableFuture.completedFuture(builder.build()) - }.playerExecuted { player, ctx -> - locator(player, colour = ctx.getArgument("color", NamedTextColor::class.java)) + .playerExecuted { player, ctx -> + player.locator(ctx.getArgument("color", NamedTextColor::class.java)) }, ).then( Commands .argument("hex", ArgumentTypes.hexColor()) .playerExecuted { player, ctx -> - locator(player, hex = ctx.getArgument("hex", TextColor::class.java)) + player.locator(ctx.getArgument("hex", TextColor::class.java)) }, ).then( Commands .literal("reset") - .playerExecuted { player, _ -> locator(player) }, + .playerExecuted { player, _ -> player.locator() }, ), "Allows players to personalise their locator bar", listOf("lc"), @@ -65,33 +58,12 @@ internal object LocatorModule : ModuleInterface { /** * Modifies the colour of a player's waypoint based on the specified parameters. - * @param player The player whose waypoint is being modified. - * @param colour The optional named colour to apply to the waypoint. - * @param hex The optional hex colour to apply to the waypoint. + * @receiver Player The player whose waypoint colour is to be modified. + * @param color The optional named colour to apply to the waypoint. */ - private fun locator( - player: Player, - colour: NamedTextColor? = null, - hex: TextColor? = null, - ) { - val cmd = "waypoint modify ${player.name}" - - when { - colour != null -> { - instance.server.dispatchCommand(player, "$cmd color $colour") - } - - hex != null -> { - instance.server.dispatchCommand( - player, - "$cmd color hex ${String.format(Locale.ENGLISH, "%06X", hex.value())}", - ) - } - - else -> { - instance.server.dispatchCommand(player, "$cmd color reset") - } - } + private fun Player.locator(color: TextColor? = null) { + waypointColor = color?.let { Color.fromRGB(it.value()) } + sendActionBar(Component.text("Locator color changed!", color)) } @Serializable From 0ce9711656ab7f24a06b3eb67f0acb6916f8934c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 00:37:01 +0000 Subject: [PATCH 17/64] [ci-skip] Update gradle/actions digest to bfd5696 (#350) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index c63ac14d7..7b8620d83 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@030fdb2d3d26e7c77770bfe796534fcd841c69dc + uses: gradle/actions/setup-gradle@bfd569614358980afc8f89c2730eee75bb97efdf - id: build-gradle name: Build with Gradle From 4caf0c36dee82a93ad77a987b27a5c47012c4e82 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 10:04:21 +0000 Subject: [PATCH 18/64] [ci-skip] Update dependency com.sk89q.worldedit:worldedit-bukkit to v7.3.18 (#351) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 70ee405a5..a8ba61eff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ repositories { dependencies { compileOnly("io.papermc.paper:paper-api:$version-R0.1-SNAPSHOT") - compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.17") // TODO("Move away from WorldEdit") + compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.18") // TODO("Move away from WorldEdit") implementation(kotlin("stdlib")) implementation("org.jetbrains.kotlin:kotlin-reflect") From a18c23378ae8cc9459bc22c1277060cff46e83fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:04:28 +0000 Subject: [PATCH 19/64] [ci-skip] Update gradle/actions digest to 6d6beca (#352) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index 7b8620d83..9ab96bd67 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@bfd569614358980afc8f89c2730eee75bb97efdf + uses: gradle/actions/setup-gradle@6d6beca4b554c6263b8a84ed7b215e8531c765be - id: build-gradle name: Build with Gradle From a42629356967cf1956d07e84732a620f09919c50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:04:47 +0000 Subject: [PATCH 20/64] [ci-skip] Update plugin com.gradleup.shadow to v9.3.1 (#353) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index a8ba61eff..88f727f99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("idea") kotlin("jvm") version "2.3.0" kotlin("plugin.serialization") version "2.3.0" - id("com.gradleup.shadow") version "9.3.0" + id("com.gradleup.shadow") version "9.3.1" id("xyz.jpenilla.run-paper") version "3.0.2" id("xyz.jpenilla.resource-factory-paper-convention") version "1.3.1" } From e2748cca0ccbc5227f438d5dddb8d2792ba9ef24 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:57:36 +0000 Subject: [PATCH 21/64] [ci-skip] Update gradle/actions digest to 576fcbe (#354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index 9ab96bd67..1bab3646a 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -29,7 +29,7 @@ jobs: - id: setup-gradle name: Setup Gradle - uses: gradle/actions/setup-gradle@6d6beca4b554c6263b8a84ed7b215e8531c765be + uses: gradle/actions/setup-gradle@576fcbecfed70890e466eeffd7c78d93b30b0472 - id: build-gradle name: Build with Gradle From 441448f1985635ee18b27ff46611c30cfcd7fd59 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 10:58:39 +0100 Subject: [PATCH 22/64] removed arrowmodule. Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 3 - .../org/xodium/vanillaplus/data/ConfigData.kt | 1 - .../xodium/vanillaplus/modules/ArrowModule.kt | 133 ------------------ .../vanillaplus/recipes/TorchArrowRecipe.kt | 87 ------------ 4 files changed, 224 deletions(-) delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/modules/ArrowModule.kt delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/recipes/TorchArrowRecipe.kt diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 15bd7bc9e..5209c5802 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -10,7 +10,6 @@ import org.xodium.vanillaplus.managers.ConfigManager import org.xodium.vanillaplus.managers.ConfigManager.load import org.xodium.vanillaplus.modules.* import org.xodium.vanillaplus.recipes.RottenFleshRecipe -import org.xodium.vanillaplus.recipes.TorchArrowRecipe import org.xodium.vanillaplus.recipes.WoodLogRecipe /** Main class of the plugin. */ @@ -46,12 +45,10 @@ internal class VanillaPlus : JavaPlugin() { listOf( RottenFleshRecipe, - TorchArrowRecipe, WoodLogRecipe, ).forEach { module -> module.register() } listOfNotNull( - ArrowModule, BooksModule, ChatModule, DimensionsModule, diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index 2a08ccd86..ce1c21e1c 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -8,7 +8,6 @@ import org.xodium.vanillaplus.modules.* /** Configuration data for the plugin. */ @Serializable internal data class ConfigData( - var arrowModule: ArrowModule.Config = ArrowModule.Config(), var booksModule: BooksModule.Config = BooksModule.Config(), var chatModule: ChatModule.Config = ChatModule.Config(), var dimensionsModule: DimensionsModule.Config = DimensionsModule.Config(), diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ArrowModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ArrowModule.kt deleted file mode 100644 index a45ef72cf..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ArrowModule.kt +++ /dev/null @@ -1,133 +0,0 @@ -package org.xodium.vanillaplus.modules - -import kotlinx.serialization.Serializable -import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.block.BlockFace -import org.bukkit.block.data.Directional -import org.bukkit.entity.Arrow -import org.bukkit.event.EventHandler -import org.bukkit.event.EventPriority -import org.bukkit.event.entity.EntityDamageByEntityEvent -import org.bukkit.event.entity.ProjectileHitEvent -import org.bukkit.event.entity.ProjectileLaunchEvent -import org.bukkit.persistence.PersistentDataType -import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.recipes.TorchArrowRecipe - -/** Represents a module handling custom arrow mechanics within the system. */ -internal object ArrowModule : ModuleInterface { - @EventHandler - fun on(event: ProjectileLaunchEvent) = handleProjectileLaunch(event) - - @EventHandler - fun on(event: ProjectileHitEvent) = handleProjectileHit(event) - - @EventHandler(priority = EventPriority.HIGHEST) - fun on(event: EntityDamageByEntityEvent) = handleEntityDamage(event) - - /** - * Applies visual tipped-arrow particles to torch arrows. - * @param event The projectile launch event. - */ - private fun handleProjectileLaunch(event: ProjectileLaunchEvent) { - val arrow = event.entity as? Arrow ?: return - val torchTypeId = arrow.torchArrowType ?: return - val torchType = TorchArrowRecipe.getTorchArrowTypeById(torchTypeId) ?: return - - arrow.color = torchType.arrowColor - } - - /** - * Handles the logic for torch arrows when they hit a target. - * @param event The projectile hit event to process. - */ - private fun handleProjectileHit(event: ProjectileHitEvent) { - val arrow = event.entity as? Arrow ?: return - val torchTypeId = arrow.torchArrowType ?: return - val torchType = TorchArrowRecipe.getTorchArrowTypeById(torchTypeId) ?: return - val hitBlock = event.hitBlock - val hitFace = event.hitBlockFace - - if (hitBlock == null || hitFace == null) { - dropArrow(arrow, arrow.location.block.location) - return - } - - val target = hitBlock.getRelative(hitFace) - - if (hitBlock.type == Material.VINE) { - hitBlock.breakNaturally() - target.type = torchType.torchMaterial - arrow.remove() - return - } - - when (hitFace) { - BlockFace.UP -> { - if (target.type == Material.AIR) { - target.type = torchType.torchMaterial - } else { - dropArrow(arrow, target.location) - } - } - - BlockFace.DOWN -> { - dropArrow(arrow, target.location) - } - - else -> { - if (target.type != Material.AIR) { - dropArrow(arrow, target.location) - return - } - - target.type = torchType.wallTorchMaterial - - val data = target.blockData as? Directional - - data?.facing = hitFace - data?.let { target.blockData = it } - } - } - - arrow.remove() - } - - /** - * Handles the logic for torch arrows when they attempt to deal damage. - * @param event The entity damage event to process. - */ - private fun handleEntityDamage(event: EntityDamageByEntityEvent) { - val arrow = event.damager as? Arrow ?: return - - if (arrow.torchArrowType == null) return - - arrow.remove() - event.isCancelled = true - } - - /** - * Drops a torch arrow item at the specified location. - * @param location The location where the torch arrow should be dropped. - * @param arrow The arrow to be dropped. - */ - private fun dropArrow( - arrow: Arrow, - location: Location, - ) { - location.world.dropItemNaturally(location, arrow.itemStack) - } - - /** - * Checks whether this arrow is a torch arrow based on its ItemStack metadata. - * @return True if the arrow is a torch arrow, false otherwise. - */ - private val Arrow.torchArrowType: String? - get() = itemStack.persistentDataContainer.get(TorchArrowRecipe.torchArrowKey, PersistentDataType.STRING) - - @Serializable - data class Config( - var enabled: Boolean = true, - ) -} diff --git a/src/main/kotlin/org/xodium/vanillaplus/recipes/TorchArrowRecipe.kt b/src/main/kotlin/org/xodium/vanillaplus/recipes/TorchArrowRecipe.kt deleted file mode 100644 index 59159d0c3..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/recipes/TorchArrowRecipe.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.xodium.vanillaplus.recipes - -import io.papermc.paper.datacomponent.DataComponentTypes -import org.bukkit.Color -import org.bukkit.Material -import org.bukkit.NamespacedKey -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.ShapelessRecipe -import org.bukkit.persistence.PersistentDataType -import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.interfaces.RecipeInterface -import org.xodium.vanillaplus.utils.ExtUtils.mm - -/** Represents an object handling torch arrow recipe implementation within the system. */ -internal object TorchArrowRecipe : RecipeInterface { - /** Namespaced key for identifying torch arrows. */ - val torchArrowKey = NamespacedKey(instance, "torch_arrow") - - /** Represents configuration for a specific torch arrow variant. */ - data class TorchArrowType( - val id: String, - val displayName: String, - val torchMaterial: Material, - val wallTorchMaterial: Material, - val arrowColor: Color, - ) - - /** List of different torch arrow types with their properties. */ - private val torchTypes = - listOf( - TorchArrowType( - "torch", - "Torch Arrow", - Material.TORCH, - Material.WALL_TORCH, - Color.YELLOW, - ), - TorchArrowType( - "soul", - "Soul Torch Arrow", - Material.SOUL_TORCH, - Material.SOUL_WALL_TORCH, - Color.BLUE, - ), - TorchArrowType( - "redstone", - "Redstone Torch Arrow", - Material.REDSTONE_TORCH, - Material.REDSTONE_WALL_TORCH, - Color.RED, - ), - TorchArrowType( - "copper", - "Copper Torch Arrow", - Material.COPPER_TORCH, - Material.COPPER_WALL_TORCH, - Color.ORANGE, - ), - ) - - /** Gets the torch arrow type configuration by ID. */ - fun getTorchArrowTypeById(id: String): TorchArrowType? = torchTypes.find { it.id == id } - - /** - * Creates a torch arrow item stack with the specified type. - * @param type The type of torch arrow to create. - * @return A pair containing the namespaced key and the created item stack. - */ - private fun createTorchArrow(type: TorchArrowType): ItemStack = - ItemStack.of(Material.ARROW).apply { - @Suppress("UnstableApiUsage") - setData(DataComponentTypes.CUSTOM_NAME, type.displayName.mm()) - editPersistentDataContainer { it.set(torchArrowKey, PersistentDataType.STRING, type.id) } - } - - override val recipes = - torchTypes - .map { type -> - ShapelessRecipe( - NamespacedKey(instance, "${type.id}_torch_arrow"), - createTorchArrow(type), - ).apply { - addIngredient(Material.ARROW) - addIngredient(type.torchMaterial) - } - }.toSet() -} From cc24dec9e462482fae70b1b8f20c9106c25ddbaf Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 12:03:32 +0100 Subject: [PATCH 23/64] Refactor pet ownership transfer logic for improved clarity and efficiency Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/PetModule.kt | 107 ++---------------- 1 file changed, 11 insertions(+), 96 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt index 07f8b27c2..e7ee4c3f3 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt @@ -1,17 +1,12 @@ package org.xodium.vanillaplus.modules import kotlinx.serialization.Serializable -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.Material -import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player import org.bukkit.entity.Tameable import org.bukkit.event.EventHandler import org.bukkit.event.player.PlayerInteractEntityEvent -import org.bukkit.inventory.ItemStack import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.ExtUtils.mm /** Represents a module handling pet mechanics within the system. */ internal object PetModule : ModuleInterface { @@ -30,109 +25,29 @@ internal object PetModule : ModuleInterface { if (source == target) return if (source.inventory.itemInMainHand.type != Material.LEAD) return - val leashedEntity = findLeashedPet(source) ?: return + val pet = source.getLeashedPet() ?: return - if (!isTransferablePet(leashedEntity, source)) return + if (!pet.isTamed || pet.owner != source) return - transferPetOwnership(source, target, leashedEntity) - event.isCancelled = true - } - - /** - * Checks if a pet can be transferred to another player. - * @param pet The tameable entity to check. - * @param owner The player attempting to transfer ownership. - * @return `true` if the pet is tamed and owned by the player, `false` otherwise. - */ - private fun isTransferablePet( - pet: Tameable, - owner: Player, - ): Boolean = pet.isTamed && pet.owner == owner - - /** - * Transfers ownership of a pet from one player to another. - * @param source The original owner of the pet. - * @param target The new owner of the pet. - * @param pet The tameable entity being transferred. - */ - private fun transferPetOwnership( - source: Player, - target: Player, - pet: Tameable, - ) { pet.owner = target - pet.setLeashHolder(null) - - returnLeadToSource(source) - notifyTransfer(source, target, pet.customName() ?: pet.name.mm()) - } + pet.setLeashHolder(target) - /** - * Attempts to return a lead to the player's inventory, dropping it if inventory is full. - * @param player The player to return the lead to. - */ - private fun returnLeadToSource(player: Player) { - player.inventory - .addItem(ItemStack(Material.LEAD)) - .takeIf { it.isNotEmpty() } - ?.let { player.world.dropItem(player.location, ItemStack(Material.LEAD)) } + event.isCancelled = true } /** - * Finds the first leashed pet owned by the player within the config radius. - * @param player The player to search around. + * Gets the first leashed pet owned by the player within the config radius. + * @receiver The player whose leashed pet is to be found. + * @param radius The radius within which to search for leashed pets. * @return The found tameable entity or `null` if none exists. */ - private fun findLeashedPet(player: Player): Tameable? = - player - .getNearbyEntities( - config.petModule.transferRadius.toDouble(), - config.petModule.transferRadius.toDouble(), - config.petModule.transferRadius.toDouble(), - ).filterIsInstance() + private fun Player.getLeashedPet(radius: Double = 10.0): Tameable? = + getNearbyEntities(radius, radius, radius) + .filterIsInstance() .firstOrNull { it.isLeashed && it.leashHolder == player } - as? Tameable - - /** - * Notifies both players about the pet transfer via action bar messages. - * @param source The original owner of the pet. - * @param target The new owner of the pet. - * @param petName The display name of the transferred pet. - */ - private fun notifyTransfer( - source: Player, - target: Player, - petName: Component, - ) { - source.sendActionBar( - config.petModule.i18n.sourceTransfer.mm( - Placeholder.component("", petName), - Placeholder.component("", target.displayName()), - ), - ) - - target.sendActionBar( - config.petModule.i18n.targetTransfer.mm( - Placeholder.component("", petName), - Placeholder.component("", source.displayName()), - ), - ) - } @Serializable data class Config( var enabled: Boolean = true, - var transferRadius: Int = 10, - var i18n: I18n = I18n(), - ) { - @Serializable - data class I18n( - var sourceTransfer: String = - "You have transferred " + - "to ", - var targetTransfer: String = - " has transferred " + - "to you", - ) - } + ) } From 74e5ece35657e88cc2e7fd2004205efae5b66ed3 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 13:10:31 +0100 Subject: [PATCH 24/64] Refactor pet interaction logic for improved clarity and consistency Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/modules/PetModule.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt index e7ee4c3f3..46622f5f1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt @@ -14,8 +14,7 @@ internal object PetModule : ModuleInterface { fun on(event: PlayerInteractEntityEvent) = handleInteractEntity(event) /** - * Handles transferring ownership of a leashed pet when a player - * right-clicks another player while holding a lead. + * Handles the interaction event when a player interacts with another entity. * @param event The [PlayerInteractEntityEvent] triggered on entity interaction. */ private fun handleInteractEntity(event: PlayerInteractEntityEvent) { @@ -25,7 +24,7 @@ internal object PetModule : ModuleInterface { if (source == target) return if (source.inventory.itemInMainHand.type != Material.LEAD) return - val pet = source.getLeashedPet() ?: return + val pet = source.getLeashedEntity() ?: return if (!pet.isTamed || pet.owner != source) return @@ -36,12 +35,12 @@ internal object PetModule : ModuleInterface { } /** - * Gets the first leashed pet owned by the player within the config radius. - * @receiver The player whose leashed pet is to be found. - * @param radius The radius within which to search for leashed pets. + * Gets the first leashed entity owned by the player within the config radius. + * @receiver The player whose leashed entity is to be found. + * @param radius The radius within which to search for leashed entities. * @return The found tameable entity or `null` if none exists. */ - private fun Player.getLeashedPet(radius: Double = 10.0): Tameable? = + private fun Player.getLeashedEntity(radius: Double = 10.0): Tameable? = getNearbyEntities(radius, radius, radius) .filterIsInstance() .firstOrNull { it.isLeashed && it.leashHolder == player } From 71e65fd97b9372261b0f17afc380c3d117115416 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 13:24:34 +0100 Subject: [PATCH 25/64] Refactor BlockUtils and PlayerUtils for improved method clarity and efficiency Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/InvModule.kt | 8 +- .../xodium/vanillaplus/modules/PetModule.kt | 13 +-- .../xodium/vanillaplus/utils/BlockUtils.kt | 27 +++--- .../org/xodium/vanillaplus/utils/ExtUtils.kt | 65 ------------- .../xodium/vanillaplus/utils/PlayerUtils.kt | 94 +++++++++++++++++-- 5 files changed, 103 insertions(+), 104 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt index 3e0983dfb..6af0fcf8c 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt @@ -22,7 +22,7 @@ import org.xodium.vanillaplus.utils.BlockUtils.center import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.ExtUtils.mm -import org.xodium.vanillaplus.utils.PlayerUtils +import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils import java.util.concurrent.CompletableFuture @@ -94,7 +94,7 @@ internal object InvModule : ModuleInterface { ) { val foundContainers = mutableListOf() - for (container in PlayerUtils.getContainersAroundPlayer(player)) { + for (container in player.getContainersAround()) { if (container.inventory.contains(material)) foundContainers.add(container.block) } @@ -120,12 +120,12 @@ internal object InvModule : ModuleInterface { Particle.TRAIL .builder() .location(player.location) - .data(Particle.Trail(container.center, Color.MAROON, 40)) + .data(Particle.Trail(container.center(), Color.MAROON, 40)) .receivers(player) .spawn() Particle.DUST .builder() - .location(container.center) + .location(container.center()) .count(10) .data(Particle.DustOptions(Color.MAROON, 5.0f)) .receivers(player) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt index 46622f5f1..7c71c2c94 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt @@ -3,10 +3,10 @@ package org.xodium.vanillaplus.modules import kotlinx.serialization.Serializable import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.entity.Tameable import org.bukkit.event.EventHandler import org.bukkit.event.player.PlayerInteractEntityEvent import org.xodium.vanillaplus.interfaces.ModuleInterface +import org.xodium.vanillaplus.utils.PlayerUtils.getLeashedEntity /** Represents a module handling pet mechanics within the system. */ internal object PetModule : ModuleInterface { @@ -34,17 +34,6 @@ internal object PetModule : ModuleInterface { event.isCancelled = true } - /** - * Gets the first leashed entity owned by the player within the config radius. - * @receiver The player whose leashed entity is to be found. - * @param radius The radius within which to search for leashed entities. - * @return The found tameable entity or `null` if none exists. - */ - private fun Player.getLeashedEntity(radius: Double = 10.0): Tameable? = - getNearbyEntities(radius, radius, radius) - .filterIsInstance() - .firstOrNull { it.isLeashed && it.leashHolder == player } - @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/BlockUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/BlockUtils.kt index d4359578d..3b76aa9da 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/BlockUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/BlockUtils.kt @@ -11,21 +11,20 @@ internal object BlockUtils { * Get the centre of a block, handling double chests properly. * @return The centre location of the block. */ - val Block.center: Location - get() { - val baseAddition = Location(location.world, location.x + 0.5, location.y + 0.5, location.z + 0.5) - val chestState = state as? Chest ?: return baseAddition - val holder = chestState.inventory.holder as? DoubleChest ?: return baseAddition - val leftBlock = (holder.leftSide as? Chest)?.block - val rightBlock = (holder.rightSide as? Chest)?.block + fun Block.center(): Location { + val baseAddition = Location(location.world, location.x + 0.5, location.y + 0.5, location.z + 0.5) + val chestState = state as? Chest ?: return baseAddition + val holder = chestState.inventory.holder as? DoubleChest ?: return baseAddition + val leftBlock = (holder.leftSide as? Chest)?.block + val rightBlock = (holder.rightSide as? Chest)?.block - if (leftBlock == null || rightBlock == null || leftBlock.world !== rightBlock.world) return baseAddition + if (leftBlock == null || rightBlock == null || leftBlock.world !== rightBlock.world) return baseAddition - val world = leftBlock.world - val cx = (leftBlock.x + rightBlock.x) / 2.0 + 0.5 - val cy = (leftBlock.y + rightBlock.y) / 2.0 + 0.5 - val cz = (leftBlock.z + rightBlock.z) / 2.0 + 0.5 + val world = leftBlock.world + val cx = (leftBlock.x + rightBlock.x) / 2.0 + 0.5 + val cy = (leftBlock.y + rightBlock.y) / 2.0 + 0.5 + val cz = (leftBlock.z + rightBlock.z) / 2.0 + 0.5 - return Location(world, cx, cy, cz) - } + return Location(world, cx, cy, cz) + } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt index 2bd2888a2..765028dd2 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt @@ -2,34 +2,17 @@ package org.xodium.vanillaplus.utils -import com.google.gson.JsonParser import io.papermc.paper.registry.TypedKey import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.bukkit.enchantments.Enchantment -import org.bukkit.entity.Player import org.xodium.vanillaplus.VanillaPlus -import java.net.URI -import java.util.* -import javax.imageio.ImageIO /** Extension utilities. */ internal object ExtUtils { private val MM: MiniMessage = MiniMessage.miniMessage() - private const val FACE_X = 8 - private const val FACE_Y = 8 - private const val FACE_WIDTH = 8 - private const val FACE_HEIGHT = 8 - private const val MAX_COORDINATE = 7 - private const val COLOR_MASK = 0xFF - private const val BLACK_COLOR = "#000000" - private const val PIXEL_CHAR = "█" - private const val ALPHA_SHIFT = 24 - private const val RED_SHIFT = 16 - private const val GREEN_SHIFT = 8 - /** The standardized prefix for [VanillaPlus] messages. */ val VanillaPlus.prefix: String get() = @@ -57,54 +40,6 @@ internal object ExtUtils { @JvmName("mmStringIterable") fun Iterable.mm(vararg resolvers: TagResolver): List = map { it.mm(*resolvers) } - /** - * Retrieves the player's face as a string. - * @param size The size of the face in pixels (default is 8). - * @return A string representing the player's face. - */ - fun Player.face(size: Int = 8): String { - // 1. fetch skin URL from the playerProfile - val texturesProp = - playerProfile.properties - .firstOrNull { it.name == "textures" } - ?: error("Player has no skin texture") - val json = JsonParser.parseString(String(Base64.getDecoder().decode(texturesProp.value))).asJsonObject - val skinUrl = - json - .getAsJsonObject("textures") - .getAsJsonObject("SKIN") - .get("url") - .asString - - // 2. load and crop - val fullImg = ImageIO.read(URI.create(skinUrl).toURL()) ?: error("Failed to load skin image from URL: $skinUrl") - val face = fullImg.getSubimage(FACE_X, FACE_Y, FACE_WIDTH, FACE_HEIGHT) - - // 3. scale & build MiniMessage - val scale = FACE_WIDTH.toDouble() / size - val builder = StringBuilder() - - for (y in 0 until size) { - for (x in 0 until size) { - val px = (x * scale).toInt().coerceAtMost(MAX_COORDINATE) - val py = (y * scale).toInt().coerceAtMost(MAX_COORDINATE) - val rgb = face.getRGB(px, py) - val a = (rgb ushr ALPHA_SHIFT) and COLOR_MASK - val r = (rgb shr RED_SHIFT) and COLOR_MASK - val g = (rgb shr GREEN_SHIFT) and COLOR_MASK - val b = rgb and COLOR_MASK - - if (a == 0) { - builder.append("$PIXEL_CHAR") - } else { - builder.append("$PIXEL_CHAR".format(r, g, b)) - } - } - builder.append("\n") - } - return builder.toString() - } - /** Extension function to convert snake_case to Proper Case with spaces. */ fun String.snakeToProperCase(): String = split('_').joinToString(" ") { word -> word.replaceFirstChar { it.uppercase() } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt index 328597147..6209ad236 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt @@ -2,20 +2,85 @@ package org.xodium.vanillaplus.utils +import com.google.gson.JsonParser import org.bukkit.Chunk import org.bukkit.block.Container import org.bukkit.entity.Player +import org.bukkit.entity.Tameable +import java.net.URI +import java.util.* +import javax.imageio.ImageIO /** Player utilities. */ internal object PlayerUtils { + private const val FACE_X = 8 + private const val FACE_Y = 8 + private const val FACE_WIDTH = 8 + private const val FACE_HEIGHT = 8 + private const val MAX_COORDINATE = 7 + private const val COLOR_MASK = 0xFF + private const val BLACK_COLOR = "#000000" + private const val PIXEL_CHAR = "█" + private const val ALPHA_SHIFT = 24 + private const val RED_SHIFT = 16 + private const val GREEN_SHIFT = 8 + /** - * Get containers around a player (3x3 area). - * @param player The player. + * Retrieves the player's face as a string. + * @param size The size of the face in pixels (default is 8). + * @return A string representing the player's face. + */ + fun Player.face(size: Int = 8): String { + // 1. fetch skin URL from the playerProfile + val texturesProp = + playerProfile.properties + .firstOrNull { it.name == "textures" } + ?: error("Player has no skin texture") + val json = JsonParser.parseString(String(Base64.getDecoder().decode(texturesProp.value))).asJsonObject + val skinUrl = + json + .getAsJsonObject("textures") + .getAsJsonObject("SKIN") + .get("url") + .asString + + // 2. load and crop + val fullImg = ImageIO.read(URI.create(skinUrl).toURL()) ?: error("Failed to load skin image from URL: $skinUrl") + val face = fullImg.getSubimage(FACE_X, FACE_Y, FACE_WIDTH, FACE_HEIGHT) + + // 3. scale & build MiniMessage + val scale = FACE_WIDTH.toDouble() / size + val builder = StringBuilder() + + for (y in 0 until size) { + for (x in 0 until size) { + val px = (x * scale).toInt().coerceAtMost(MAX_COORDINATE) + val py = (y * scale).toInt().coerceAtMost(MAX_COORDINATE) + val rgb = face.getRGB(px, py) + val a = (rgb ushr ALPHA_SHIFT) and COLOR_MASK + val r = (rgb shr RED_SHIFT) and COLOR_MASK + val g = (rgb shr GREEN_SHIFT) and COLOR_MASK + val b = rgb and COLOR_MASK + + if (a == 0) { + builder.append("$PIXEL_CHAR") + } else { + builder.append("$PIXEL_CHAR".format(r, g, b)) + } + } + builder.append("\n") + } + return builder.toString() + } + + /** + * Get containers around this player (3x3 chunk area). + * @receiver The player. * @return Collection of containers around the player. */ - fun getContainersAroundPlayer(player: Player): Set = + fun Player.getContainersAround(): Set = buildSet { - for (chunk in getChunksAroundPlayer(player)) { + for (chunk in getChunksAround()) { for (state in chunk.tileEntities) { if (state is Container) add(state) } @@ -23,19 +88,30 @@ internal object PlayerUtils { } /** - * Get chunks around a player (3x3 area). - * @param player The player. + * Get chunks around this player (3x3 chunk area). + * @receiver The player. * @return Collection of chunks around the player. */ - fun getChunksAroundPlayer(player: Player): Set { - val (baseX, baseZ) = player.location.chunk.run { x to z } + fun Player.getChunksAround(): Set { + val (baseX, baseZ) = location.chunk.run { x to z } return buildSet { for (x in -1..1) { for (z in -1..1) { - add(player.world.getChunkAt(baseX + x, baseZ + z)) + add(world.getChunkAt(baseX + x, baseZ + z)) } } } } + + /** + * Gets the first leashed entity owned by the player within the config radius. + * @receiver The player whose leashed entity is to be found. + * @param radius The radius within which to search for leashed entities. + * @return The found tameable entity or `null` if none exists. + */ + fun Player.getLeashedEntity(radius: Double = 10.0): Tameable? = + getNearbyEntities(radius, radius, radius) + .filterIsInstance() + .firstOrNull { it.isLeashed && it.leashHolder == player } } From 29277cf2d96e449898d2da8229d9d608fce1a1a5 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 13:25:33 +0100 Subject: [PATCH 26/64] Refactor ChatModule to use PlayerUtils for improved method clarity Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 5bc3a6079..364732679 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -21,9 +21,9 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.ExtUtils.face import org.xodium.vanillaplus.utils.ExtUtils.mm import org.xodium.vanillaplus.utils.ExtUtils.prefix +import org.xodium.vanillaplus.utils.PlayerUtils.face import java.util.concurrent.CompletableFuture /** Represents a module handling chat mechanics within the system. */ From 03873db73f7c18993208d9684cf1f1f1f2b9ce89 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 13:30:44 +0100 Subject: [PATCH 27/64] Refactor imports to replace ExtUtils with Utils for consistency across modules Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt | 2 +- .../vanillaplus/enchantments/FeatherFallingEnchantment.kt | 5 ++++- .../vanillaplus/enchantments/NightVisionEnchantment.kt | 2 +- .../org/xodium/vanillaplus/enchantments/NimbusEnchantment.kt | 2 +- .../org/xodium/vanillaplus/enchantments/PickupEnchantment.kt | 2 +- .../xodium/vanillaplus/enchantments/ReplantEnchantment.kt | 2 +- .../xodium/vanillaplus/enchantments/VeinMineEnchantment.kt | 2 +- .../kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt | 4 ++-- src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt | 4 ++-- .../org/xodium/vanillaplus/modules/DimensionsModule.kt | 2 +- src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt | 2 +- src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt | 2 +- .../kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt | 2 +- src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt | 2 +- .../kotlin/org/xodium/vanillaplus/modules/TabListModule.kt | 2 +- src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt | 4 ++-- .../org/xodium/vanillaplus/utils/{ExtUtils.kt => Utils.kt} | 4 ++-- 17 files changed, 24 insertions(+), 21 deletions(-) rename src/main/kotlin/org/xodium/vanillaplus/utils/{ExtUtils.kt => Utils.kt} (97%) diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt index e9a8b8a16..6d54da0d4 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable import net.kyori.adventure.inventory.Book import org.bukkit.permissions.PermissionDefault import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm /** * Represents the data structure for a book in the game. diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt index d190e349b..f82462be3 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt @@ -28,5 +28,8 @@ internal object FeatherFallingEnchantment : EnchantmentInterface { * @return `true` if the item is a pickaxe with Silk Touch, otherwise `false`. */ private fun isValidTool(item: ItemStack?): Boolean = - item?.let { Tag.ITEMS_FOOT_ARMOR.isTagged(it.type) && it.containsEnchantment(Enchantment.FEATHER_FALLING) } == true + item?.let { + Tag.ITEMS_FOOT_ARMOR.isTagged(it.type) && + it.containsEnchantment(Enchantment.FEATHER_FALLING) + } == true } diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/NightVisionEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/NightVisionEnchantment.kt index 7ab7329f8..00a155ea2 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/NightVisionEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/NightVisionEnchantment.kt @@ -7,7 +7,7 @@ import org.bukkit.inventory.EquipmentSlotGroup import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.xodium.vanillaplus.interfaces.EnchantmentInterface -import org.xodium.vanillaplus.utils.ExtUtils.displayName +import org.xodium.vanillaplus.utils.Utils.displayName /** Represents an object handling night vision enchantment implementation within the system. */ @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/NimbusEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/NimbusEnchantment.kt index 81519b856..56ae2da59 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/NimbusEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/NimbusEnchantment.kt @@ -7,7 +7,7 @@ import org.bukkit.entity.HappyGhast import org.bukkit.inventory.EquipmentSlot import org.bukkit.inventory.EquipmentSlotGroup import org.xodium.vanillaplus.interfaces.EnchantmentInterface -import org.xodium.vanillaplus.utils.ExtUtils.displayName +import org.xodium.vanillaplus.utils.Utils.displayName /** Represents an object handling nimbus enchantment implementation within the system. */ @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/PickupEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/PickupEnchantment.kt index 46ad2b6d1..e2270b929 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/PickupEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/PickupEnchantment.kt @@ -6,7 +6,7 @@ import org.bukkit.entity.Player import org.bukkit.event.block.BlockDropItemEvent import org.bukkit.inventory.EquipmentSlotGroup import org.xodium.vanillaplus.interfaces.EnchantmentInterface -import org.xodium.vanillaplus.utils.ExtUtils.displayName +import org.xodium.vanillaplus.utils.Utils.displayName /** Represents an object handling pickup enchantment implementation within the system. */ @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt index 26b1a337a..5f085c18f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt @@ -6,7 +6,7 @@ import org.bukkit.event.block.BlockBreakEvent import org.bukkit.inventory.EquipmentSlotGroup import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.EnchantmentInterface -import org.xodium.vanillaplus.utils.ExtUtils.displayName +import org.xodium.vanillaplus.utils.Utils.displayName /** Represents an object handling replant enchantment implementation within the system. */ @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt index 3d5a78de1..f3265f203 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/VeinMineEnchantment.kt @@ -10,7 +10,7 @@ import org.bukkit.event.block.BlockBreakEvent import org.bukkit.inventory.EquipmentSlotGroup import org.bukkit.inventory.meta.Damageable import org.xodium.vanillaplus.interfaces.EnchantmentInterface -import org.xodium.vanillaplus.utils.ExtUtils.displayName +import org.xodium.vanillaplus.utils.Utils.displayName /** Represents an object handling vein mine enchantment implementation within the system. */ @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index 4752e7b44..2cc4a99f9 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -14,8 +14,8 @@ import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.strategies.CapitalizedStrategy import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.ExtUtils.mm -import org.xodium.vanillaplus.utils.ExtUtils.prefix +import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.prefix import java.io.File import kotlin.time.measureTime diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 364732679..88135aab8 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -21,9 +21,9 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.ExtUtils.mm -import org.xodium.vanillaplus.utils.ExtUtils.prefix import org.xodium.vanillaplus.utils.PlayerUtils.face +import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.prefix import java.util.concurrent.CompletableFuture /** Represents a module handling chat mechanics within the system. */ diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt index 8c0e97d8c..388d61055 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt @@ -14,7 +14,7 @@ import org.bukkit.event.player.PlayerTeleportEvent import org.bukkit.event.world.PortalCreateEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm import kotlin.math.pow import kotlin.math.sqrt diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt index 6af0fcf8c..aa889848b 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt @@ -21,9 +21,9 @@ import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.BlockUtils.center import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import org.xodium.vanillaplus.utils.ExtUtils.mm import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils +import org.xodium.vanillaplus.utils.Utils.mm import java.util.concurrent.CompletableFuture /** Represents a module handling inv mechanics within the system. */ diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt index b83922350..af793f756 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.server.ServerListPingEvent import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm /** Represents a module handling MOTD mechanics within the system. */ internal object MotdModule : ModuleInterface { diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 8fc026474..9d35740d1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -33,7 +33,7 @@ import org.xodium.vanillaplus.enchantments.* import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.pdcs.PlayerPDC.nickname import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm /** Represents a module handling player mechanics within the system. */ internal object PlayerModule : ModuleInterface { diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt index 59eb5bd12..b0f506b87 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt @@ -12,7 +12,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm /** Represents a module handling sign mechanics within the system. */ internal object SignModule : ModuleInterface { diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index a917083a3..7b24c1f83 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -15,7 +15,7 @@ import org.bukkit.event.weather.ThunderChangeEvent import org.bukkit.event.weather.WeatherChangeEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.ExtUtils.mm +import org.xodium.vanillaplus.utils.Utils.mm import java.util.* import kotlin.math.roundToInt diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt index 9522af6ce..94d9fe933 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt @@ -6,8 +6,8 @@ import com.mojang.brigadier.context.CommandContext import io.papermc.paper.command.brigadier.CommandSourceStack import org.bukkit.entity.Player import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.utils.ExtUtils.mm -import org.xodium.vanillaplus.utils.ExtUtils.prefix +import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.prefix /** Utility functions for command handling. */ internal object CommandUtils { diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt similarity index 97% rename from src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt rename to src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt index 765028dd2..c64fa1e6f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/ExtUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt @@ -9,8 +9,8 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.bukkit.enchantments.Enchantment import org.xodium.vanillaplus.VanillaPlus -/** Extension utilities. */ -internal object ExtUtils { +/** General utilities. */ +internal object Utils { private val MM: MiniMessage = MiniMessage.miniMessage() /** The standardized prefix for [VanillaPlus] messages. */ From c81f851f60d3ded87a3b05fff1fc34a34f9ffb91 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 13:52:13 +0100 Subject: [PATCH 28/64] Refactor PetModule to TameableModule for improved clarity and consistency Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt | 2 +- src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt | 2 +- .../vanillaplus/modules/{PetModule.kt => TameableModule.kt} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/kotlin/org/xodium/vanillaplus/modules/{PetModule.kt => TameableModule.kt} (90%) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 5209c5802..fd02efab7 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -57,12 +57,12 @@ internal class VanillaPlus : JavaPlugin() { LocatorModule, MotdModule, OpenableModule, - PetModule, PlayerModule, ScoreBoardModule, SignModule, SitModule, TabListModule, + TameableModule, if (WorldEditHook.get()) TreesModule else null, ).forEach { module -> module.register() } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index ce1c21e1c..a0fea7d5b 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -16,11 +16,11 @@ internal data class ConfigData( var locatorModule: LocatorModule.Config = LocatorModule.Config(), var motdModule: MotdModule.Config = MotdModule.Config(), var openableModule: OpenableModule.Config = OpenableModule.Config(), - var petModule: PetModule.Config = PetModule.Config(), var playerModule: PlayerModule.Config = PlayerModule.Config(), var scoreboardModule: ScoreBoardModule.Config = ScoreBoardModule.Config(), var signModule: SignModule.Config = SignModule.Config(), var sitModule: SitModule.Config = SitModule.Config(), var tabListModule: TabListModule.Config = TabListModule.Config(), + var tameableModule: TameableModule.Config = TameableModule.Config(), var treesModule: TreesModule.Config = TreesModule.Config(), ) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt similarity index 90% rename from src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt rename to src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt index 7c71c2c94..6a5c0484c 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PetModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt @@ -8,8 +8,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.PlayerUtils.getLeashedEntity -/** Represents a module handling pet mechanics within the system. */ -internal object PetModule : ModuleInterface { +/** Represents a module handling tameable mechanics within the system. */ +internal object TameableModule : ModuleInterface { @EventHandler fun on(event: PlayerInteractEntityEvent) = handleInteractEntity(event) From 5f9fc27f1a2f3f59aeda444c0420346cac51e353 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 14:20:31 +0100 Subject: [PATCH 29/64] Move locator functionality to PlayerUtils for better organization and clarity Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/LocatorModule.kt | 14 +------------- .../org/xodium/vanillaplus/utils/PlayerUtils.kt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt index 6594d1a52..e157f1a39 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt @@ -5,17 +5,15 @@ package org.xodium.vanillaplus.modules import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.command.brigadier.argument.ArgumentTypes import kotlinx.serialization.Serializable -import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor -import org.bukkit.Color -import org.bukkit.entity.Player 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.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted +import org.xodium.vanillaplus.utils.PlayerUtils.locator /** Represents a module handling locator mechanics within the system. */ internal object LocatorModule : ModuleInterface { @@ -56,16 +54,6 @@ internal object LocatorModule : ModuleInterface { ), ) - /** - * Modifies the colour of a player's waypoint based on the specified parameters. - * @receiver Player The player whose waypoint colour is to be modified. - * @param color The optional named colour to apply to the waypoint. - */ - private fun Player.locator(color: TextColor? = null) { - waypointColor = color?.let { Color.fromRGB(it.value()) } - sendActionBar(Component.text("Locator color changed!", color)) - } - @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt index 6209ad236..8ffee5122 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt @@ -3,7 +3,10 @@ package org.xodium.vanillaplus.utils import com.google.gson.JsonParser +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor import org.bukkit.Chunk +import org.bukkit.Color import org.bukkit.block.Container import org.bukkit.entity.Player import org.bukkit.entity.Tameable @@ -114,4 +117,14 @@ internal object PlayerUtils { getNearbyEntities(radius, radius, radius) .filterIsInstance() .firstOrNull { it.isLeashed && it.leashHolder == player } + + /** + * Modifies the colour of a player's waypoint based on the specified parameters. + * @receiver Player The player whose waypoint colour is to be modified. + * @param color The optional named colour to apply to the waypoint. + */ + fun Player.locator(color: TextColor? = null) { + waypointColor = color?.let { Color.fromRGB(it.value()) } + sendActionBar(Component.text("Locator color changed!", color)) + } } From 8e9a89e44ed23959a5a8592a41e79a2b73be0334 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 14:40:34 +0100 Subject: [PATCH 30/64] Refactor event handler methods for consistency and clarity across modules Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/ChatModule.kt | 28 +++- .../vanillaplus/modules/DimensionsModule.kt | 12 +- .../vanillaplus/modules/OpenableModule.kt | 4 +- .../vanillaplus/modules/PlayerModule.kt | 154 ++++++++---------- .../vanillaplus/modules/ScoreBoardModule.kt | 4 +- .../xodium/vanillaplus/modules/SitModule.kt | 16 +- .../vanillaplus/modules/TameableModule.kt | 4 +- .../xodium/vanillaplus/modules/TreesModule.kt | 4 +- 8 files changed, 115 insertions(+), 111 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 88135aab8..a269679fd 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -83,7 +83,19 @@ internal object ChatModule : ModuleInterface { ) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: AsyncChatEvent) { + fun on(event: AsyncChatEvent) = asyncChat(event) + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun on(event: PlayerJoinEvent) = playerJoin(event) + + @EventHandler + fun on(event: PlayerSetSpawnEvent) = playerSetSpawn(event) + + /** + * Handles asynchronous chat events. + * @param event The [AsyncChatEvent] to be processed. + */ + private fun asyncChat(event: AsyncChatEvent) { event.renderer(ChatRenderer.defaultRenderer()) event.renderer { player, displayName, message, audience -> var base = @@ -107,8 +119,11 @@ internal object ChatModule : ModuleInterface { } } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - fun on(event: PlayerJoinEvent) { + /** + * Handles player join events. + * @param event The [PlayerJoinEvent] to be processed. + */ + private fun playerJoin(event: PlayerJoinEvent) { val player = event.player var imageIndex = 0 @@ -127,8 +142,11 @@ internal object ChatModule : ModuleInterface { ) } - @EventHandler - fun on(event: PlayerSetSpawnEvent) { + /** + * Handles player set spawn events. + * @param event The [PlayerSetSpawnEvent] to be processed. + */ + private fun playerSetSpawn(event: PlayerSetSpawnEvent) { event.notification = config.chatModule.i18n.playerSetSpawn.mm( Placeholder.component( diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt index 388d61055..c11c1c2c0 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt @@ -23,19 +23,19 @@ internal object DimensionsModule : ModuleInterface { private const val NETHER_TO_OVERWORLD_RATIO = 8 @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerPortalEvent) = handlePlayerPortal(event) + fun on(event: PlayerPortalEvent) = playerPortal(event) @EventHandler(priority = EventPriority.HIGH) - fun on(event: EntityPortalEvent) = handleEntityPortal(event) + fun on(event: EntityPortalEvent) = entityPortal(event) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PortalCreateEvent) = handlePortalCreate(event) + fun on(event: PortalCreateEvent) = portalCreate(event) /** * Handles the PlayerPortalEvent to prevent portal creation in the Nether. * @param event The PlayerPortalEvent to handle. */ - private fun handlePlayerPortal(event: PlayerPortalEvent) { + private fun playerPortal(event: PlayerPortalEvent) { if (event.cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL) { if (event.player.world.environment == World.Environment.NETHER) event.canCreatePortal = false } @@ -45,7 +45,7 @@ internal object DimensionsModule : ModuleInterface { * Handles the EntityPortalEvent to prevent portal creation in the Nether. * @param event The EntityPortalEvent to handle. */ - private fun handleEntityPortal(event: EntityPortalEvent) { + private fun entityPortal(event: EntityPortalEvent) { if (event.entity.world.environment == World.Environment.NETHER) event.canCreatePortal = false } @@ -53,7 +53,7 @@ internal object DimensionsModule : ModuleInterface { * Handles the PortalCreateEvent to prevent portal creation in the Nether if no corresponding Overworld portal exists. * @param event The PortalCreateEvent to handle. */ - private fun handlePortalCreate(event: PortalCreateEvent) { + private fun portalCreate(event: PortalCreateEvent) { if (event.world.environment == World.Environment.NETHER && event.reason == PortalCreateEvent.CreateReason.FIRE ) { diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt index f5deed0b6..bad700aea 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt @@ -41,13 +41,13 @@ internal object OpenableModule : ModuleInterface { ) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerInteractEvent) = handleInteract(event) + fun on(event: PlayerInteractEvent) = playerInteract(event) /** * Handles block interactions and delegates to the correct click handler. * @param event The [PlayerInteractEvent] triggered by the player. */ - private fun handleInteract(event: PlayerInteractEvent) { + private fun playerInteract(event: PlayerInteractEvent) { val clickedBlock = event.clickedBlock ?: return if (!isValidInteraction(event)) return diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 9d35740d1..ad4bc619e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -5,7 +5,6 @@ package org.xodium.vanillaplus.modules import com.mojang.brigadier.arguments.StringArgumentType import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.datacomponent.DataComponentTypes -import io.papermc.paper.datacomponent.item.ItemLore import io.papermc.paper.datacomponent.item.ResolvableProfile import io.papermc.paper.event.entity.EntityEquipmentChangedEvent import kotlinx.serialization.Serializable @@ -68,7 +67,44 @@ internal object PlayerModule : ModuleInterface { ) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerJoinEvent) { + fun on(event: PlayerJoinEvent) = playerJoin(event) + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + fun on(event: PlayerQuitEvent) = playerQuit(event) + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + fun on(event: PlayerDeathEvent) = playerDeath(event) + + @EventHandler + fun on(event: PlayerAdvancementDoneEvent) = playerAdvancementDone(event) + + @EventHandler + fun on(event: InventoryClickEvent) = enderchest(event) + + @EventHandler(ignoreCancelled = true) + fun on(event: PlayerInteractEvent) { + xpToBottle(event) + FeatherFallingEnchantment.featherFalling(event) + } + + @EventHandler + fun on(event: BlockBreakEvent) { + ReplantEnchantment.replant(event) + SilkTouchEnchantment.silkTouch(event) + VeinMineEnchantment.veinMine(event) + } + + @EventHandler + fun on(event: BlockDropItemEvent) = PickupEnchantment.pickup(event) + + @EventHandler + fun on(event: EntityEquipmentChangedEvent) = NightVisionEnchantment.nightVision(event) + + /** + * Handles the event when a player joins the game. + * @param event The PlayerJoinEvent triggered when a player joins. + */ + private fun playerJoin(event: PlayerJoinEvent) { val player = event.player player.displayName(player.nickname?.mm()) @@ -92,8 +128,30 @@ internal object PlayerModule : ModuleInterface { } } - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerQuitEvent) { + /** + * Handles the event when a player dies. + * @param event The PlayerDeathEvent triggered when a player dies. + */ + private fun playerDeath(event: PlayerDeathEvent) { + if (Math.random() < config.playerModule.skullDropChance) { + event.entity.world.dropItemNaturally( + event.entity.location, + @Suppress("UnstableApiUsage") + ItemStack.of(Material.PLAYER_HEAD).apply { + setData(DataComponentTypes.PROFILE, ResolvableProfile.resolvableProfile(event.entity.playerProfile)) + }, + ) + } + // TODO +// if (config.playerFeature.i18n.playerDeathMsg.isNotEmpty()) event.deathMessage() +// if (config.playerFeature.i18n.playerDeathScreenMsg.isNotEmpty()) event.deathScreenMessageOverride() + } + + /** + * Handles the event when a player quits the game. + * @param event The PlayerQuitEvent triggered when a player quits the game. + */ + private fun playerQuit(event: PlayerQuitEvent) { if (config.playerModule.i18n.playerQuitMsg .isEmpty() ) { @@ -110,23 +168,11 @@ internal object PlayerModule : ModuleInterface { ) } - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerDeathEvent) { - val killer = event.entity.killer ?: return - - if (Math.random() < config.playerModule.skullDropChance) { - event.entity.world.dropItemNaturally( - event.entity.location, - playerSkull(event.entity, killer), - ) - } - // TODO -// if (config.playerFeature.i18n.playerDeathMsg.isNotEmpty()) event.deathMessage() -// if (config.playerFeature.i18n.playerDeathScreenMsg.isNotEmpty()) event.deathScreenMessageOverride() - } - - @EventHandler - fun on(event: PlayerAdvancementDoneEvent) { + /** + * Handles the event when a player completes an advancement. + * @param event The PlayerAdvancementDoneEvent triggered when a player completes an advancement. + */ + private fun playerAdvancementDone(event: PlayerAdvancementDoneEvent) { if (config.playerModule.i18n.playerAdvancementDoneMsg .isEmpty() ) { @@ -141,34 +187,6 @@ internal object PlayerModule : ModuleInterface { ) } - @EventHandler - fun on(event: InventoryClickEvent) { - enderchest(event) - } - - @EventHandler(ignoreCancelled = true) - fun on(event: PlayerInteractEvent) { - xpToBottle(event) - FeatherFallingEnchantment.featherFalling(event) - } - - @EventHandler - fun on(event: BlockBreakEvent) { - ReplantEnchantment.replant(event) - SilkTouchEnchantment.silkTouch(event) - VeinMineEnchantment.veinMine(event) - } - - @EventHandler - fun on(event: BlockDropItemEvent) { - PickupEnchantment.pickup(event) - } - - @EventHandler - fun on(event: EntityEquipmentChangedEvent) { - NightVisionEnchantment.nightVision(event) - } - /** * Handles the inventory click event where a player can open their ender chest by clicking on an ender chest item * in their inventory. @@ -238,42 +256,11 @@ internal object PlayerModule : ModuleInterface { ) } - /** - * Creates a custom player skull item when a player is killed. - * @param entity The player whose head is being created. - * @param killer The player who killed the entity. - * @return An [ItemStack] representing the customized player head. - */ - @Suppress("UnstableApiUsage") - private fun playerSkull( - entity: Player, - killer: Player, - ): ItemStack = - ItemStack.of(Material.PLAYER_HEAD).apply { - setData(DataComponentTypes.PROFILE, ResolvableProfile.resolvableProfile(entity.playerProfile)) - setData( - DataComponentTypes.CUSTOM_NAME, - config.playerModule.i18n.playerHeadName - .mm(Placeholder.component("player", entity.name.mm())), - ) - setData( - DataComponentTypes.LORE, - ItemLore - .lore( - config.playerModule.i18n.playerHeadLore - .mm( - Placeholder.component("player", entity.name.mm()), - Placeholder.component("killer", killer.name.mm()), - ), - ), - ) - } - @Serializable data class Config( var enabled: Boolean = true, var enderChestClickType: ClickType = ClickType.SHIFT_RIGHT, - var skullDropChance: Double = 0.1, + var skullDropChance: Double = 0.01, var xpCostToBottle: Int = 11, var silkTouch: SilkTouchEnchantment = SilkTouchEnchantment(), var i18n: I18n = I18n(), @@ -286,8 +273,6 @@ internal object PlayerModule : ModuleInterface { @Serializable data class I18n( - var playerHeadName: String = "’s Skull", - var playerHeadLore: List = listOf(" killed by "), // var playerDeathMsg: String = " ", var playerJoinMsg: String = " ", var playerQuitMsg: String = " ", @@ -296,7 +281,8 @@ internal object PlayerModule : ModuleInterface { var playerAdvancementDoneMsg: String = "\uD83C\uDF89 " + "has made the advancement: ", - var nicknameUpdated: String = "Nickname has been updated to: ", + var nicknameUpdated: String = + "Nickname has been updated to: ", ) } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt index a023f244c..79c0516e9 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt @@ -37,13 +37,13 @@ internal object ScoreBoardModule : ModuleInterface { ) @EventHandler - fun on(event: PlayerJoinEvent) = handleJoin(event) + fun on(event: PlayerJoinEvent) = playerJoin(event) /** * Applies the correct scoreboard to players when they join. * @param event The [PlayerJoinEvent] triggered when the player joins. */ - private fun handleJoin(event: PlayerJoinEvent) { + private fun playerJoin(event: PlayerJoinEvent) { event.player.scoreboard = if (event.player.scoreboardVisibility == true) { instance.server.scoreboardManager.newScoreboard diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index 275bb1efb..8ddd55894 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -29,22 +29,22 @@ internal object SitModule : ModuleInterface { private val playerStandUpOffset = Vector(0.0, 0.5, 0.0) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - fun on(event: PlayerInteractEvent) = handleInteract(event) + fun on(event: PlayerInteractEvent) = playerInteract(event) @EventHandler - fun on(event: EntityDismountEvent) = handleDismount(event) + fun on(event: EntityDismountEvent) = entityDismount(event) @EventHandler - fun on(event: PlayerQuitEvent) = handleQuit(event) + fun on(event: PlayerQuitEvent) = playerQuit(event) @EventHandler(ignoreCancelled = true) - fun on(event: EntityDamageEvent) = handleDamage(event) + fun on(event: EntityDamageEvent) = entityDamage(event) /** * Handles player interaction to initiate sitting. * @param event The [PlayerInteractEvent] triggered by the player. */ - private fun handleInteract(event: PlayerInteractEvent) { + private fun playerInteract(event: PlayerInteractEvent) { val player = event.player if (event.action != Action.RIGHT_CLICK_BLOCK || player.isSneaking || player.isInsideVehicle) return @@ -72,7 +72,7 @@ internal object SitModule : ModuleInterface { * Handles dismounting from the sitting ArmorStand. * @param event The [EntityDismountEvent] triggered when the player dismounts. */ - private fun handleDismount(event: EntityDismountEvent) { + private fun entityDismount(event: EntityDismountEvent) { val player = event.entity as? Player ?: return sittingPlayers.remove(player.uniqueId)?.let { armorStand -> @@ -88,7 +88,7 @@ internal object SitModule : ModuleInterface { * Handles clean-up when a player quits. * @param event The [PlayerQuitEvent] triggered when the player leaves the server. */ - private fun handleQuit(event: PlayerQuitEvent) { + private fun playerQuit(event: PlayerQuitEvent) { sittingPlayers.remove(event.player.uniqueId)?.remove() } @@ -96,7 +96,7 @@ internal object SitModule : ModuleInterface { * Handles player damage while sitting. * @param event The [EntityDamageEvent] triggered when the player takes damage. */ - private fun handleDamage(event: EntityDamageEvent) { + private fun entityDamage(event: EntityDamageEvent) { val player = event.entity as? Player ?: return sittingPlayers[player.uniqueId]?.let { stand -> diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt index 6a5c0484c..af1d588f0 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt @@ -11,13 +11,13 @@ import org.xodium.vanillaplus.utils.PlayerUtils.getLeashedEntity /** Represents a module handling tameable mechanics within the system. */ internal object TameableModule : ModuleInterface { @EventHandler - fun on(event: PlayerInteractEntityEvent) = handleInteractEntity(event) + fun on(event: PlayerInteractEntityEvent) = playerInteractEntity(event) /** * Handles the interaction event when a player interacts with another entity. * @param event The [PlayerInteractEntityEvent] triggered on entity interaction. */ - private fun handleInteractEntity(event: PlayerInteractEntityEvent) { + private fun playerInteractEntity(event: PlayerInteractEntityEvent) { val source = event.player val target = event.rightClicked as? Player ?: return diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt index 54ca4dbb9..bd544a7e9 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt @@ -35,14 +35,14 @@ internal object TreesModule : ModuleInterface { } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: StructureGrowEvent) = handleStructureGrow(event) + fun on(event: StructureGrowEvent) = structureGrow(event) /** * Handles StructureGrowEvent and attempts to paste a schematic * when the grown block is a sapling or fungus. * @param event The [StructureGrowEvent] triggered by natural growth. */ - private fun handleStructureGrow(event: StructureGrowEvent) { + private fun structureGrow(event: StructureGrowEvent) { event.location.block .takeIf { Tag.SAPLINGS.isTagged(it.type) || From 62aa53acafdf47b12ef0a9c3641bb600e6d4c3ed Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 14:50:02 +0100 Subject: [PATCH 31/64] Refactor PlayerModule and TabListModule for improved clarity and functionality Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 1 + .../vanillaplus/modules/PlayerModule.kt | 4 --- .../vanillaplus/modules/TabListModule.kt | 32 ++++++------------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index d8376ca9c..c6ebc3353 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -40,6 +40,7 @@ shulker signedit spellbite + tablist tellraw temurin triumphteam diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index ad4bc619e..80301557d 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -36,8 +36,6 @@ import org.xodium.vanillaplus.utils.Utils.mm /** Represents a module handling player mechanics within the system. */ internal object PlayerModule : ModuleInterface { - private val tabListModule by lazy { TabListModule } - override val cmds = listOf( CommandData( @@ -244,8 +242,6 @@ internal object PlayerModule : ModuleInterface { ) { player.nickname = name player.displayName(player.nickname?.mm()) - // TODO: add enabled check. - tabListModule.updatePlayerDisplayName(player) player.sendActionBar( config.playerModule.i18n.nicknameUpdated.mm( Placeholder.component( diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index 7b24c1f83..686f293bb 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -7,10 +7,8 @@ import net.kyori.adventure.audience.Audience import net.kyori.adventure.text.Component import net.kyori.adventure.text.JoinConfiguration import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder -import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority -import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.weather.ThunderChangeEvent import org.bukkit.event.weather.WeatherChangeEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance @@ -28,42 +26,30 @@ internal object TabListModule : ModuleInterface { private const val COLOR_FORMAT = "#%02X%02X%02X" init { - instance.server.onlinePlayers.forEach { - updateTabList(it) - updatePlayerDisplayName(it) - } - // TPS Check. instance.server.scheduler.runTaskTimer( instance, - Runnable { instance.server.onlinePlayers.forEach { updateTabList(it) } }, + Runnable { + instance.server.onlinePlayers.forEach { player -> + tablist(player) + player.playerListName(player.displayName()) + } + }, config.tabListModule.initDelayInTicks, config.tabListModule.intervalInTicks, ) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - fun on(event: PlayerJoinEvent) { - updateTabList(event.player) - updatePlayerDisplayName(event.player) - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - fun on(event: WeatherChangeEvent) = event.world.players.forEach { updateTabList(it) } + fun on(event: WeatherChangeEvent) = event.world.players.forEach { player -> tablist(player) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - fun on(event: ThunderChangeEvent) = event.world.players.forEach { updateTabList(it) } - - /** - * Update the player's display name in the tab list. - * @param player the player to update. - */ - fun updatePlayerDisplayName(player: Player): Unit = player.playerListName(player.displayName()) + fun on(event: ThunderChangeEvent) = event.world.players.forEach { player -> tablist(player) } /** * Update the tab list for the given audience. * @param audience the audience to update the tab list for. */ - private fun updateTabList(audience: Audience) { + private fun tablist(audience: Audience) { val joinConfig = JoinConfiguration.separator(Component.newline()) audience.sendPlayerListHeaderAndFooter( From 39e6b910b51ace8c9c5b40e456a6eb60f759f0e1 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 15:37:33 +0100 Subject: [PATCH 32/64] Refactor ChatModule and related files to use MM.deserialize for message formatting Signed-off-by: Illyrius --- .../vanillaplus/managers/ConfigManager.kt | 6 +- .../xodium/vanillaplus/modules/ChatModule.kt | 82 +++++++------------ .../vanillaplus/modules/DimensionsModule.kt | 31 ++----- .../xodium/vanillaplus/modules/InvModule.kt | 21 ++--- .../xodium/vanillaplus/modules/MotdModule.kt | 9 +- .../vanillaplus/modules/PlayerModule.kt | 28 +++---- .../xodium/vanillaplus/modules/SignModule.kt | 4 +- .../vanillaplus/modules/TabListModule.kt | 5 +- .../xodium/vanillaplus/utils/CommandUtils.kt | 4 +- .../org/xodium/vanillaplus/utils/Utils.kt | 18 +--- 10 files changed, 77 insertions(+), 131 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index 2cc4a99f9..ce581745a 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -14,7 +14,7 @@ import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.strategies.CapitalizedStrategy import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix import java.io.File import kotlin.time.measureTime @@ -42,7 +42,9 @@ internal object ConfigManager { instance.logger.warning("Command can only be executed by a Player!") } configData = ConfigData().load("config.json") - it.source.sender.sendMessage("${instance.prefix} configuration reloaded!".mm()) + it.source.sender.sendMessage( + MM.deserialize("${instance.prefix} configuration reloaded!"), + ) }, ), "Allows to plugin specific admin commands", diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index a269679fd..2606e1bfb 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -22,7 +22,7 @@ import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.PlayerUtils.face -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix import java.util.concurrent.CompletableFuture @@ -59,8 +59,7 @@ internal object ChatModule : ModuleInterface { instance.server .getPlayer(targetName) ?: return@executesCatching sender.sendMessage( - config.chatModule.i18n.playerIsNotOnline - .mm(), + MM.deserialize(config.chatModule.i18n.playerIsNotOnline), ) val message = it.getArgument("message", String().javaClass) @@ -99,21 +98,20 @@ internal object ChatModule : ModuleInterface { event.renderer(ChatRenderer.defaultRenderer()) event.renderer { player, displayName, message, audience -> var base = - config.chatModule.chatFormat.mm( - Placeholder.component("player_head", "".mm()), + MM.deserialize( + config.chatModule.chatFormat, + Placeholder.component("player_head", MM.deserialize("")), Placeholder.component( "player", displayName .clickEvent(ClickEvent.suggestCommand("/w ${player.name} ")) .hoverEvent( - HoverEvent.showText( - config.chatModule.i18n.clickToWhisper - .mm(), - ), + HoverEvent.showText(MM.deserialize(config.chatModule.i18n.clickToWhisper)), ), ), Placeholder.component("message", message), ) + if (audience == player) base = base.appendSpace().append(createDeleteCross(event.signedMessage())) base } @@ -129,16 +127,15 @@ internal object ChatModule : ModuleInterface { var imageIndex = 0 player.sendMessage( - Regex("") - .replace(config.chatModule.welcomeText.joinToString("\n")) { "" } - .mm( - Placeholder.component("player", player.displayName()), - *player - .face() - .lines() - .mapIndexed { i, line -> Placeholder.component("image${i + 1}", line.mm()) } - .toTypedArray(), - ), + MM.deserialize( + Regex("").replace(config.chatModule.welcomeText.joinToString("\n")) { "" }, + Placeholder.component("player", player.displayName()), + *player + .face() + .lines() + .mapIndexed { i, line -> Placeholder.component("image${i + 1}", MM.deserialize(line)) } + .toTypedArray(), + ), ) } @@ -148,11 +145,9 @@ internal object ChatModule : ModuleInterface { */ private fun playerSetSpawn(event: PlayerSetSpawnEvent) { event.notification = - config.chatModule.i18n.playerSetSpawn.mm( - Placeholder.component( - "notification", - event.notification ?: return, - ), + MM.deserialize( + config.chatModule.i18n.playerSetSpawn, + Placeholder.component("notification", event.notification ?: return), ) } @@ -168,38 +163,30 @@ internal object ChatModule : ModuleInterface { message: String, ) { sender.sendMessage( - config.chatModule.whisperToFormat.mm( + MM.deserialize( + config.chatModule.whisperToFormat, Placeholder.component( "player", target .displayName() .clickEvent(ClickEvent.suggestCommand("/w ${target.name} ")) - .hoverEvent( - HoverEvent.showText( - config.chatModule.i18n.clickToWhisper - .mm(), - ), - ), + .hoverEvent(HoverEvent.showText(MM.deserialize(config.chatModule.i18n.clickToWhisper))), ), - Placeholder.component("message", message.mm()), + Placeholder.component("message", MM.deserialize(message)), ), ) target.sendMessage( - config.chatModule.whisperFromFormat.mm( + MM.deserialize( + config.chatModule.whisperFromFormat, Placeholder.component( "player", sender .displayName() .clickEvent(ClickEvent.suggestCommand("/w ${sender.name} ")) - .hoverEvent( - HoverEvent.showText( - config.chatModule.i18n.clickToWhisper - .mm(), - ), - ), + .hoverEvent(HoverEvent.showText(MM.deserialize(config.chatModule.i18n.clickToWhisper))), ), - Placeholder.component("message", message.mm()), + Placeholder.component("message", MM.deserialize(message)), ), ) } @@ -210,17 +197,10 @@ internal object ChatModule : ModuleInterface { * @return A [net.kyori.adventure.text.Component] representing the delete cross with hover text and click action. */ private fun createDeleteCross(signedMessage: SignedMessage): Component = - config.chatModule.deleteCross - .mm() - .hoverEvent( - config.chatModule.i18n.deleteMessage - .mm(), - ).clickEvent( - ClickEvent.callback { - instance.server - .deleteMessage(signedMessage) - }, - ) + MM + .deserialize(config.chatModule.deleteCross) + .hoverEvent(MM.deserialize(config.chatModule.i18n.deleteMessage)) + .clickEvent(ClickEvent.callback { instance.server.deleteMessage(signedMessage) }) @Serializable data class Config( diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt index c11c1c2c0..cf52c9d57 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt @@ -14,9 +14,8 @@ import org.bukkit.event.player.PlayerTeleportEvent import org.bukkit.event.world.PortalCreateEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.Utils.mm -import kotlin.math.pow -import kotlin.math.sqrt +import org.xodium.vanillaplus.utils.Utils.MM +import kotlin.math.hypot /** Represents a module handling dimension mechanics within the system. */ internal object DimensionsModule : ModuleInterface { @@ -60,10 +59,11 @@ internal object DimensionsModule : ModuleInterface { if (findCorrespondingPortal(calcPortalCentre(event.blocks), getOverworld()) == null) { event.isCancelled = true val player = event.entity as? Player ?: return - player.sendActionBar( - config.dimensionsModule.i18n.portalCreationDenied - .mm(), - ) + val overworld = getOverworld() + val destination = player.respawnLocation?.takeIf { it.world == overworld } ?: overworld.spawnLocation + + player.sendActionBar(MM.deserialize(config.dimensionsModule.i18n.portalCreationDenied)) + player.teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN) } } } @@ -96,7 +96,7 @@ internal object DimensionsModule : ModuleInterface { for (y in 0..overworld.maxHeight) { val block = overworld.getBlockAt(x, y, z) if (block.type == Material.NETHER_PORTAL) { - val dist = distance2D(targetX, targetZ, x.toDouble(), z.toDouble()) + val dist = hypot(targetX - x.toDouble(), targetZ - z.toDouble()) if (dist < closestDistance) { closestDistance = dist closestPortal = block.location @@ -108,21 +108,6 @@ internal object DimensionsModule : ModuleInterface { return closestPortal } - /** - * Calculates the 2D Euclidean distance between two points in the X-Z plane. - * @param x1 The X-coordinate of the first point. - * @param z1 The Z-coordinate of the first point. - * @param x2 The X-coordinate of the second point. - * @param z2 The Z-coordinate of the second point. - * @return The distance between the two points. - */ - private fun distance2D( - x1: Double, - z1: Double, - x2: Double, - z2: Double, - ): Double = sqrt((x1 - x2).pow(2) + (z1 - z2).pow(2)) - /** * Calculates the centre point of a portal structure by averaging the positions of its constituent blocks. * @param blockStates The list of [org.bukkit.block.BlockState]s representing the portal frame and portal blocks. diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt index aa889848b..255ae90d1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt @@ -23,7 +23,7 @@ import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM import java.util.concurrent.CompletableFuture /** Represents a module handling inv mechanics within the system. */ @@ -71,10 +71,7 @@ internal object InvModule : ModuleInterface { materialName?.let { Material.getMaterial(it.uppercase()) } ?: player.inventory.itemInMainHand.type if (material == Material.AIR) { - player.sendActionBar( - config.invModule.i18n.noMaterialSpecified - .mm(), - ) + player.sendActionBar(MM.deserialize(config.invModule.i18n.noMaterialSpecified)) return 0 } @@ -100,19 +97,19 @@ internal object InvModule : ModuleInterface { if (foundContainers.isEmpty()) { player.sendActionBar( - config.invModule.i18n.noMatchingItems.mm( - Placeholder.component( - "material", - material.name.mm(), - ), + MM.deserialize( + config.invModule.i18n.noMatchingItems, + Placeholder.component("material", MM.deserialize(material.name)), ), ) return } player.sendActionBar( - config.invModule.i18n.foundItemsInChests - .mm(Placeholder.component("material", material.name.mm())), + MM.deserialize( + config.invModule.i18n.foundItemsInChests, + Placeholder.component("material", MM.deserialize(material.name)), + ), ) ScheduleUtils.schedule(duration = 200L) { diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt index af793f756..62b5079cc 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.server.ServerListPingEvent import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM /** Represents a module handling MOTD mechanics within the system. */ internal object MotdModule : ModuleInterface { @@ -16,12 +16,7 @@ internal object MotdModule : ModuleInterface { * Sets the MOTD for the server list ping event. * @param event The server list ping event. */ - private fun motd(event: ServerListPingEvent) = - event.motd( - config.motdModule.motd - .joinToString("\n") - .mm(), - ) + private fun motd(event: ServerListPingEvent) = event.motd(MM.deserialize(config.motdModule.motd.joinToString("\n"))) @Serializable data class Config( diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 80301557d..d62605d0b 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -32,7 +32,7 @@ import org.xodium.vanillaplus.enchantments.* import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.pdcs.PlayerPDC.nickname import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM /** Represents a module handling player mechanics within the system. */ internal object PlayerModule : ModuleInterface { @@ -105,7 +105,7 @@ internal object PlayerModule : ModuleInterface { private fun playerJoin(event: PlayerJoinEvent) { val player = event.player - player.displayName(player.nickname?.mm()) + player.displayName(MM.deserialize(player.nickname ?: player.name)) if (config.playerModule.i18n.playerJoinMsg .isEmpty() @@ -119,7 +119,8 @@ internal object PlayerModule : ModuleInterface { .filter { it.uniqueId != player.uniqueId } .forEach { it.sendMessage( - config.playerModule.i18n.playerJoinMsg.mm( + MM.deserialize( + config.playerModule.i18n.playerJoinMsg, Placeholder.component("player", player.displayName()), ), ) @@ -157,11 +158,9 @@ internal object PlayerModule : ModuleInterface { } event.quitMessage( - config.playerModule.i18n.playerQuitMsg.mm( - Placeholder.component( - "player", - event.player.displayName(), - ), + MM.deserialize( + config.playerModule.i18n.playerQuitMsg, + Placeholder.component("player", event.player.displayName()), ), ) } @@ -178,7 +177,8 @@ internal object PlayerModule : ModuleInterface { } event.message( - config.playerModule.i18n.playerAdvancementDoneMsg.mm( + MM.deserialize( + config.playerModule.i18n.playerAdvancementDoneMsg, Placeholder.component("player", event.player.displayName()), Placeholder.component("advancement", event.advancement.displayName()), ), @@ -241,13 +241,11 @@ internal object PlayerModule : ModuleInterface { name: String, ) { player.nickname = name - player.displayName(player.nickname?.mm()) + player.displayName(MM.deserialize(player.nickname ?: player.name)) player.sendActionBar( - config.playerModule.i18n.nicknameUpdated.mm( - Placeholder.component( - "nickname", - player.displayName(), - ), + MM.deserialize( + config.playerModule.i18n.nicknameUpdated, + Placeholder.component("nickname", player.displayName()), ), ) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt index b0f506b87..c1f176f8a 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt @@ -12,7 +12,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM /** Represents a module handling sign mechanics within the system. */ internal object SignModule : ModuleInterface { @@ -65,7 +65,7 @@ internal object SignModule : ModuleInterface { val sign = target.state as Sign val signSide = sign.getSide(sign.getInteractableSideFor(player)) - signSide.line(line, text.mm()) + signSide.line(line, MM.deserialize(text)) sign.update() } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index 686f293bb..2568478d8 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -13,6 +13,7 @@ import org.bukkit.event.weather.ThunderChangeEvent import org.bukkit.event.weather.WeatherChangeEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface +import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.mm import java.util.* import kotlin.math.roundToInt @@ -57,8 +58,8 @@ internal object TabListModule : ModuleInterface { Component.join( joinConfig, config.tabListModule.footer.mm( - Placeholder.component("weather", getWeather().mm()), - Placeholder.component("tps", getTps().mm()), + Placeholder.component("weather", MM.deserialize(getWeather())), + Placeholder.component("tps", MM.deserialize(getTps())), ), ), ) diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt index 94d9fe933..5e2b23071 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/CommandUtils.kt @@ -6,7 +6,7 @@ import com.mojang.brigadier.context.CommandContext import io.papermc.paper.command.brigadier.CommandSourceStack import org.bukkit.entity.Player import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix /** Utility functions for command handling. */ @@ -30,7 +30,7 @@ internal object CommandUtils { """.trimIndent(), ) (ctx.source.sender as? Player)?.sendMessage( - "${instance.prefix} An error has occurred. Check server logs for details.".mm(), + MM.deserialize("${instance.prefix} An error has occurred. Check server logs for details."), ) } Command.SINGLE_SUCCESS diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt index c64fa1e6f..3513efaad 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt @@ -11,7 +11,7 @@ import org.xodium.vanillaplus.VanillaPlus /** General utilities. */ internal object Utils { - private val MM: MiniMessage = MiniMessage.miniMessage() + val MM: MiniMessage = MiniMessage.miniMessage() /** The standardized prefix for [VanillaPlus] messages. */ val VanillaPlus.prefix: String @@ -20,30 +20,18 @@ internal object Utils { "${this.javaClass.simpleName}" + "]" - /** - * Deserializes a [MiniMessage] [String] into a [Component]. - * @param resolvers Optional tag resolvers for custom tags. - * @return The deserialized [Component]. - */ - fun String.mm(vararg resolvers: TagResolver): Component = - if (resolvers.isEmpty()) { - MM.deserialize(this) - } else { - MM.deserialize(this, TagResolver.resolver(*resolvers)) - } - /** * Deserializes an iterable collection of [MiniMessage] strings into a list of Components. * @param resolvers Optional tag resolvers for custom tags. * @return The list of deserialized Components. */ @JvmName("mmStringIterable") - fun Iterable.mm(vararg resolvers: TagResolver): List = map { it.mm(*resolvers) } + fun Iterable.mm(vararg resolvers: TagResolver): List = map { MM.deserialize(it, *resolvers) } /** Extension function to convert snake_case to Proper Case with spaces. */ fun String.snakeToProperCase(): String = split('_').joinToString(" ") { word -> word.replaceFirstChar { it.uppercase() } } /** Extension function specifically for enchantment keys */ - fun TypedKey.displayName(): Component = value().snakeToProperCase().mm() + fun TypedKey.displayName(): Component = MM.deserialize(value().snakeToProperCase()) } From 9021f350b2c3117d88a8b65e8b4e27085e178171 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 15:45:10 +0100 Subject: [PATCH 33/64] Refactor BookData and TabListModule to use MM.deserialize for message formatting Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/data/BookData.kt | 5 +++-- .../xodium/vanillaplus/modules/TabListModule.kt | 17 +++++------------ .../org/xodium/vanillaplus/utils/Utils.kt | 10 +--------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt index 6d54da0d4..b6daf0277 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/BookData.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable import net.kyori.adventure.inventory.Book import org.bukkit.permissions.PermissionDefault import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.utils.Utils.mm +import org.xodium.vanillaplus.utils.Utils.MM /** * Represents the data structure for a book in the game. @@ -26,5 +26,6 @@ internal data class BookData( * Converts this [BookData] instance to a [Book] instance. * @return A [Book] instance with the properties of this [BookData]. */ - fun toBook(): Book = Book.book(title.mm(), author.mm(), pages.map { it.joinToString("\n").mm() }) + fun toBook(): Book = + Book.book(MM.deserialize(title), MM.deserialize(author), pages.map { MM.deserialize(it.joinToString("\n")) }) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index 2568478d8..9bd7ef0ae 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -4,8 +4,6 @@ package org.xodium.vanillaplus.modules import kotlinx.serialization.Serializable import net.kyori.adventure.audience.Audience -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.JoinConfiguration import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority @@ -14,7 +12,6 @@ import org.bukkit.event.weather.WeatherChangeEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.Utils.MM -import org.xodium.vanillaplus.utils.Utils.mm import java.util.* import kotlin.math.roundToInt @@ -51,16 +48,12 @@ internal object TabListModule : ModuleInterface { * @param audience the audience to update the tab list for. */ private fun tablist(audience: Audience) { - val joinConfig = JoinConfiguration.separator(Component.newline()) - audience.sendPlayerListHeaderAndFooter( - Component.join(joinConfig, config.tabListModule.header.mm()), - Component.join( - joinConfig, - config.tabListModule.footer.mm( - Placeholder.component("weather", MM.deserialize(getWeather())), - Placeholder.component("tps", MM.deserialize(getTps())), - ), + MM.deserialize(config.tabListModule.header.joinToString("\n")), + MM.deserialize( + config.tabListModule.footer.joinToString("\n"), + Placeholder.component("weather", MM.deserialize(getWeather())), + Placeholder.component("tps", MM.deserialize(getTps())), ), ) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt index 3513efaad..d4c664c66 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt @@ -5,12 +5,12 @@ package org.xodium.vanillaplus.utils import io.papermc.paper.registry.TypedKey import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.bukkit.enchantments.Enchantment import org.xodium.vanillaplus.VanillaPlus /** General utilities. */ internal object Utils { + /** MiniMessage instance for parsing formatted strings. */ val MM: MiniMessage = MiniMessage.miniMessage() /** The standardized prefix for [VanillaPlus] messages. */ @@ -20,14 +20,6 @@ internal object Utils { "${this.javaClass.simpleName}" + "]" - /** - * Deserializes an iterable collection of [MiniMessage] strings into a list of Components. - * @param resolvers Optional tag resolvers for custom tags. - * @return The list of deserialized Components. - */ - @JvmName("mmStringIterable") - fun Iterable.mm(vararg resolvers: TagResolver): List = map { MM.deserialize(it, *resolvers) } - /** Extension function to convert snake_case to Proper Case with spaces. */ fun String.snakeToProperCase(): String = split('_').joinToString(" ") { word -> word.replaceFirstChar { it.uppercase() } } From eed2242ba009cbda26ed3a0421107aa6235ec316 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 7 Jan 2026 19:30:00 +0100 Subject: [PATCH 34/64] Add block break event handling to remove sitting ArmorStands Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/SitModule.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index 8ddd55894..d6949dbaa 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -14,6 +14,7 @@ import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.block.Action +import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.entity.EntityDamageEvent import org.bukkit.event.entity.EntityDismountEvent import org.bukkit.event.player.PlayerInteractEvent @@ -40,6 +41,9 @@ internal object SitModule : ModuleInterface { @EventHandler(ignoreCancelled = true) fun on(event: EntityDamageEvent) = entityDamage(event) + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun on(event: BlockBreakEvent) = blockBreak(event) + /** * Handles player interaction to initiate sitting. * @param event The [PlayerInteractEvent] triggered by the player. @@ -105,6 +109,28 @@ internal object SitModule : ModuleInterface { } } + /** + * Handles block break events to remove sitting ArmorStands on broken blocks. + * @param event The [BlockBreakEvent] triggered when a block is broken. + */ + private fun blockBreak(event: BlockBreakEvent) { + val brokenBlockLocation = event.block.location + + sittingPlayers.entries.removeIf { (_, armorStand) -> + val armorStandBlock = armorStand.location.subtract(blockCenterOffset).block + + if (armorStandBlock.location == brokenBlockLocation) { + armorStand.passengers + .filterIsInstance() + .forEach { player -> armorStand.removePassenger(player) } + armorStand.remove() + true + } else { + false + } + } + } + /** * Spawns an invisible, marker ArmorStand at the given location and makes the player sit on it. * @param player The [Player] who will be made to sit. From 09dad9d9e47c23c03b1b5b4a06c4542382958979 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 8 Jan 2026 08:17:45 +0100 Subject: [PATCH 35/64] + Signed-off-by: Illyrius --- .github/workflows/codeql.yml | 61 ++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4cf310edb..8f217eb9b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,27 +22,42 @@ jobs: fail-fast: false matrix: include: - - language: actions - build-mode: none - - language: java-kotlin - build-mode: manual - + - language: actions + build-mode: none + - language: java-kotlin + build-mode: manual + steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: ./gradlew shadowJar - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" + - id: checkout + name: Checkout + uses: actions/checkout@v6.0.1 + + - id: setup-java + name: Setup Java + uses: actions/setup-java@v5 + with: + java-version: 21 + distribution: temurin + + - id: setup-gradle + name: Setup Gradle + uses: gradle/actions/setup-gradle@576fcbecfed70890e466eeffd7c78d93b30b0472 + + - id: init-codeql + name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - id: build-gradle + name: Build with Gradle + if: matrix.build-mode == 'manual' + shell: bash + run: ./gradlew shadowJar + + - id: perform-codeql-analysis + name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From 50ecca4fe9ed3c7c1ab2afded0482885f17ae775 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:15:38 +0100 Subject: [PATCH 36/64] feat/EntityModule/RandomAnimalSizes (#356) * init Signed-off-by: Illyrius * Add TODO comments for potential enhancements in animal size randomization Signed-off-by: Illyrius * Refactor entity size randomization to support multiple entity types Signed-off-by: Illyrius * Refactor entity scale randomization to use range objects for size limits Signed-off-by: Illyrius * Adjust scale ranges for monsters and villagers in entity module Signed-off-by: Illyrius * + Signed-off-by: Illyrius * + Signed-off-by: Illyrius * Refactor entity scale ranges to use Utils.Range class for improved consistency Signed-off-by: Illyrius --------- Signed-off-by: Illyrius Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 1 + .../vanillaplus/modules/EntityModule.kt | 34 ++++++++++++++++++- .../org/xodium/vanillaplus/utils/Utils.kt | 17 ++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index c6ebc3353..d0eec73bb 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -39,6 +39,7 @@ searchinv shulker signedit + slenderman spellbite tablist tellraw diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt index fd2c9063c..2352c2044 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt @@ -5,14 +5,17 @@ package org.xodium.vanillaplus.modules import io.papermc.paper.event.entity.EntityEquipmentChangedEvent import kotlinx.serialization.Serializable import org.bukkit.Material +import org.bukkit.attribute.Attribute import org.bukkit.entity.* import org.bukkit.event.EventHandler import org.bukkit.event.entity.EntityChangeBlockEvent import org.bukkit.event.entity.EntityDeathEvent import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.entity.EntitySpawnEvent import org.bukkit.inventory.ItemStack import org.xodium.vanillaplus.enchantments.NimbusEnchantment import org.xodium.vanillaplus.interfaces.ModuleInterface +import org.xodium.vanillaplus.utils.Utils import kotlin.random.Random /** Represents a module handling entity mechanics within the system. */ @@ -37,6 +40,9 @@ internal object EntityModule : ModuleInterface { @EventHandler fun on(event: EntityEquipmentChangedEvent) = NimbusEnchantment.nimbus(event) + @EventHandler + fun on(event: EntitySpawnEvent) = randomizeEntityScale(event.entity) + /** * Determines whether an entity's griefing behaviour should be cancelled based on configuration settings. * @param entity The entity whose griefing behaviour is being evaluated. @@ -54,6 +60,29 @@ internal object EntityModule : ModuleInterface { else -> false } + /** + * Randomizes the scale of certain entities based on configuration settings. + * @param entity The entity whose scale should be randomized. + */ + private fun randomizeEntityScale(entity: Entity) { + when (entity) { + is Animals -> { + val range = config.entityModule.animalScaleRange ?: return + entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) + } + + is Monster -> { + val range = config.entityModule.monsterScaleRange ?: return + entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) + } + + is Villager -> { + val range = config.entityModule.villagerScaleRange ?: return + entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) + } + } + } + @Serializable data class Config( var enabled: Boolean = true, @@ -63,6 +92,9 @@ internal object EntityModule : ModuleInterface { var disableEndermanGrief: Boolean = true, var disableGhastGrief: Boolean = true, var disableWitherGrief: Boolean = true, - var entityEggDropChance: Double = 0.1, + var entityEggDropChance: Double = 0.001, + var animalScaleRange: Utils.Range? = Utils.Range(0.8, 1.2), + var monsterScaleRange: Utils.Range? = Utils.Range(0.9, 1.9), + var villagerScaleRange: Utils.Range? = Utils.Range(0.9, 1.1), ) } diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt index d4c664c66..42d08475e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/Utils.kt @@ -3,6 +3,7 @@ package org.xodium.vanillaplus.utils import io.papermc.paper.registry.TypedKey +import kotlinx.serialization.Serializable import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage import org.bukkit.enchantments.Enchantment @@ -26,4 +27,20 @@ internal object Utils { /** Extension function specifically for enchantment keys */ fun TypedKey.displayName(): Component = MM.deserialize(value().snakeToProperCase()) + + /** + * Data class representing a numerical range with minimum and maximum values. + * @property min The minimum value of the range. + * @property max The maximum value of the range. + * @throws IllegalArgumentException if [min] is greater than [max]. + */ + @Serializable + data class Range( + val min: Double, + val max: Double, + ) { + init { + require(min <= max) { "min must be less than or equal to max" } + } + } } From 47400c0316cf0a7ec079dfc6761c6ca22706b258 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 8 Jan 2026 11:02:50 +0100 Subject: [PATCH 37/64] refactor: update command argument handling to use new argument types Signed-off-by: Illyrius --- .../vanillaplus/managers/ConfigManager.kt | 2 +- .../xodium/vanillaplus/modules/ChatModule.kt | 19 +++++++------------ .../xodium/vanillaplus/modules/InvModule.kt | 19 ++++++------------- .../vanillaplus/modules/OpenableModule.kt | 1 + .../vanillaplus/modules/PlayerModule.kt | 3 ++- .../xodium/vanillaplus/modules/SitModule.kt | 19 ++++++++++--------- .../vanillaplus/modules/TabListModule.kt | 5 ++--- .../xodium/vanillaplus/modules/TreesModule.kt | 1 + 8 files changed, 30 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index ce581745a..36715473f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -16,7 +16,7 @@ import org.xodium.vanillaplus.strategies.CapitalizedStrategy import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix -import java.io.File +import java.io.File // TODO: replace with kotlin.io.path import kotlin.time.measureTime /** Manages loading and saving the configuration file. */ diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 2606e1bfb..36b14d7a7 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -4,6 +4,8 @@ import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent import com.mojang.brigadier.arguments.StringArgumentType import io.papermc.paper.chat.ChatRenderer import io.papermc.paper.command.brigadier.Commands +import io.papermc.paper.command.brigadier.argument.ArgumentTypes +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver import io.papermc.paper.event.player.AsyncChatEvent import kotlinx.serialization.Serializable import net.kyori.adventure.chat.SignedMessage @@ -24,7 +26,6 @@ import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.PlayerUtils.face import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix -import java.util.concurrent.CompletableFuture /** Represents a module handling chat mechanics within the system. */ internal object ChatModule : ModuleInterface { @@ -36,14 +37,8 @@ internal object ChatModule : ModuleInterface { .requires { it.sender.hasPermission(perms[0]) } .then( Commands - .argument("target", StringArgumentType.string()) - .suggests { _, builder -> - instance.server.onlinePlayers - .map { it.name } - .filter { it.lowercase().startsWith(builder.remaining.lowercase()) } - .forEach(builder::suggest) - CompletableFuture.completedFuture(builder.build()) - }.then( + .argument("target", ArgumentTypes.player()) + .then( Commands .argument("message", StringArgumentType.greedyString()) .executesCatching { @@ -54,10 +49,10 @@ internal object ChatModule : ModuleInterface { } val sender = it.source.sender as Player - val targetName = it.getArgument("target", String().javaClass) + val targetResolver = + it.getArgument("target", PlayerSelectorArgumentResolver::class.java) val target = - instance.server - .getPlayer(targetName) + targetResolver.resolve(it.source).singleOrNull() ?: return@executesCatching sender.sendMessage( MM.deserialize(config.chatModule.i18n.playerIsNotOnline), ) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt index 255ae90d1..278f12d53 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt @@ -2,10 +2,11 @@ package org.xodium.vanillaplus.modules -import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.context.CommandContext import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands +import io.papermc.paper.command.brigadier.argument.ArgumentTypes +import io.papermc.paper.registry.RegistryKey import kotlinx.serialization.Serializable import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.Color @@ -24,7 +25,6 @@ import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils import org.xodium.vanillaplus.utils.Utils.MM -import java.util.concurrent.CompletableFuture /** Represents a module handling inv mechanics within the system. */ internal object InvModule : ModuleInterface { @@ -36,14 +36,8 @@ internal object InvModule : ModuleInterface { .requires { it.sender.hasPermission(perms[0]) } .then( Commands - .argument("material", StringArgumentType.word()) - .suggests { _, builder -> - Material.entries - .map { it.name.lowercase() } - .filter { it.startsWith(builder.remaining.lowercase()) } - .forEach(builder::suggest) - CompletableFuture.completedFuture(builder.build()) - }.playerExecuted { _, ctx -> handleSearch(ctx) }, + .argument("material", ArgumentTypes.resource(RegistryKey.ITEM)) + .playerExecuted { _, ctx -> handleSearch(ctx) }, ).executesCatching { handleSearch(it) }, "Search nearby chests for specific items", listOf("search", "searchinv", "invs"), @@ -66,9 +60,9 @@ internal object InvModule : ModuleInterface { */ private fun handleSearch(ctx: CommandContext): Int { val player = ctx.source.sender as? Player ?: return 0 - val materialName = runCatching { StringArgumentType.getString(ctx, "material") }.getOrNull() val material = - materialName?.let { Material.getMaterial(it.uppercase()) } ?: player.inventory.itemInMainHand.type + runCatching { ctx.getArgument("material", Material::class.java) }.getOrNull() + ?: player.inventory.itemInMainHand.type if (material == Material.AIR) { player.sendActionBar(MM.deserialize(config.invModule.i18n.noMaterialSpecified)) @@ -76,7 +70,6 @@ internal object InvModule : ModuleInterface { } search(player, material) - return 1 } diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt index bad700aea..bfebee18e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt @@ -23,6 +23,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.AdjacentBlockData import org.xodium.vanillaplus.data.SoundData import org.xodium.vanillaplus.interfaces.ModuleInterface +// TODO: replace. import java.util.* /** Represents a module handling openable blocks mechanics within the system. */ diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index d62605d0b..0e0751c18 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -33,6 +33,7 @@ import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.pdcs.PlayerPDC.nickname import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.Utils.MM +import kotlin.random.Random /** Represents a module handling player mechanics within the system. */ internal object PlayerModule : ModuleInterface { @@ -132,7 +133,7 @@ internal object PlayerModule : ModuleInterface { * @param event The PlayerDeathEvent triggered when a player dies. */ private fun playerDeath(event: PlayerDeathEvent) { - if (Math.random() < config.playerModule.skullDropChance) { + if (Random.nextDouble() <= config.playerModule.skullDropChance) { event.entity.world.dropItemNaturally( event.entity.location, @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index d6949dbaa..d46db36d6 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -1,5 +1,3 @@ -@file:Suppress("ktlint:standard:no-wildcard-imports") - package org.xodium.vanillaplus.modules import kotlinx.serialization.Serializable @@ -21,11 +19,14 @@ import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.util.Vector import org.xodium.vanillaplus.interfaces.ModuleInterface -import java.util.* +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid +import kotlin.uuid.toKotlinUuid /** Represents a module handling sit mechanics within the system. */ +@OptIn(ExperimentalUuidApi::class) internal object SitModule : ModuleInterface { - private val sittingPlayers = mutableMapOf() + private val sittingPlayers = mutableMapOf() private val blockCenterOffset = Vector(0.5, 0.5, 0.5) private val playerStandUpOffset = Vector(0.0, 0.5, 0.0) @@ -79,7 +80,7 @@ internal object SitModule : ModuleInterface { private fun entityDismount(event: EntityDismountEvent) { val player = event.entity as? Player ?: return - sittingPlayers.remove(player.uniqueId)?.let { armorStand -> + sittingPlayers.remove(player.uniqueId.toKotlinUuid())?.let { armorStand -> val safe = armorStand.location.clone().add(playerStandUpOffset) safe.yaw = player.location.yaw safe.pitch = player.location.pitch @@ -93,7 +94,7 @@ internal object SitModule : ModuleInterface { * @param event The [PlayerQuitEvent] triggered when the player leaves the server. */ private fun playerQuit(event: PlayerQuitEvent) { - sittingPlayers.remove(event.player.uniqueId)?.remove() + sittingPlayers.remove(event.player.uniqueId.toKotlinUuid())?.remove() } /** @@ -103,9 +104,9 @@ internal object SitModule : ModuleInterface { private fun entityDamage(event: EntityDamageEvent) { val player = event.entity as? Player ?: return - sittingPlayers[player.uniqueId]?.let { stand -> + sittingPlayers[player.uniqueId.toKotlinUuid()]?.let { stand -> stand.removePassenger(player) - sittingPlayers.remove(player.uniqueId) + sittingPlayers.remove(player.uniqueId.toKotlinUuid()) } } @@ -150,7 +151,7 @@ internal object SitModule : ModuleInterface { } armorStand.addPassenger(player) - sittingPlayers[player.uniqueId] = armorStand + sittingPlayers[player.uniqueId.toKotlinUuid()] = armorStand } @Serializable diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index 9bd7ef0ae..b7fc05e87 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -12,7 +12,6 @@ import org.bukkit.event.weather.WeatherChangeEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.Utils.MM -import java.util.* import kotlin.math.roundToInt /** Represents a module handling tab-list mechanics within the system. */ @@ -67,7 +66,7 @@ internal object TabListModule : ModuleInterface { val clampedTps = tps.coerceIn(MIN_TPS, MAX_TPS) val ratio = clampedTps / MAX_TPS val color = getColorForTps(ratio) - val formattedTps = String.format(Locale.ENGLISH, TPS_DECIMAL_FORMAT, tps) + val formattedTps = TPS_DECIMAL_FORMAT.format(tps) return "$formattedTps" } @@ -82,7 +81,7 @@ internal object TabListModule : ModuleInterface { val r = (MAX_COLOR_VALUE * (1 - clamped)).roundToInt() val g = (MAX_COLOR_VALUE * clamped).roundToInt() - return String.format(Locale.ENGLISH, COLOR_FORMAT, r, g, 0) + return COLOR_FORMAT.format(r, g, 0) } /** diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt index bd544a7e9..92eb16098 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt @@ -19,6 +19,7 @@ import org.bukkit.event.world.StructureGrowEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.registries.MaterialRegistry +// TODO: replace. import java.io.IOException import java.nio.channels.Channels import java.nio.channels.ReadableByteChannel From c796d62ec32b634f5324402a188028503c4aed52 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 8 Jan 2026 11:08:55 +0100 Subject: [PATCH 38/64] refactor: replace Java file handling with Kotlin path utilities and clean up imports Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/managers/ConfigManager.kt | 8 +++++--- .../org/xodium/vanillaplus/modules/OpenableModule.kt | 4 +--- .../kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt index 36715473f..3b0be7848 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt @@ -1,5 +1,7 @@ @file:OptIn(ExperimentalSerializationApi::class) +@file:Suppress("ktlint:standard:no-wildcard-imports") + package org.xodium.vanillaplus.managers import io.papermc.paper.command.brigadier.Commands @@ -16,7 +18,7 @@ import org.xodium.vanillaplus.strategies.CapitalizedStrategy import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.Utils.MM import org.xodium.vanillaplus.utils.Utils.prefix -import java.io.File // TODO: replace with kotlin.io.path +import kotlin.io.path.* import kotlin.time.measureTime /** Manages loading and saving the configuration file. */ @@ -64,9 +66,9 @@ internal object ConfigManager { * @return The loaded configuration data. */ inline fun T.load(fileName: String): T { - val file = File(instance.dataFolder, fileName) + val file = instance.dataFolder.toPath() / fileName - if (!instance.dataFolder.exists()) instance.dataFolder.mkdirs() + if (!instance.dataFolder.toPath().exists()) instance.dataFolder.toPath().createDirectories() val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else this diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt index bfebee18e..ffd877d37 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt @@ -23,12 +23,10 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.AdjacentBlockData import org.xodium.vanillaplus.data.SoundData import org.xodium.vanillaplus.interfaces.ModuleInterface -// TODO: replace. -import java.util.* /** Represents a module handling openable blocks mechanics within the system. */ internal object OpenableModule : ModuleInterface { - private val disallowedKnockGameModes = EnumSet.of(GameMode.CREATIVE, GameMode.SPECTATOR) + private val disallowedKnockGameModes = setOf(GameMode.CREATIVE, GameMode.SPECTATOR) private val possibleNeighbours: Set = setOf( AdjacentBlockData(0, -1, Door.Hinge.RIGHT, BlockFace.EAST), diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt index 8ffee5122..53bf55493 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt @@ -14,6 +14,8 @@ import java.net.URI import java.util.* import javax.imageio.ImageIO +// TODO: replace java with kotlin + /** Player utilities. */ internal object PlayerUtils { private const val FACE_X = 8 From 0c99b6b7fa5938614df5d3ee1b0e1a8d0b159afa Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 8 Jan 2026 11:13:19 +0100 Subject: [PATCH 39/64] refactor: update PlayerUtils to use Kotlin's Base64 encoding and clean up imports Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt index 53bf55493..dab4ceba1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/utils/PlayerUtils.kt @@ -11,10 +11,8 @@ import org.bukkit.block.Container import org.bukkit.entity.Player import org.bukkit.entity.Tameable import java.net.URI -import java.util.* import javax.imageio.ImageIO - -// TODO: replace java with kotlin +import kotlin.io.encoding.Base64 /** Player utilities. */ internal object PlayerUtils { @@ -41,7 +39,7 @@ internal object PlayerUtils { playerProfile.properties .firstOrNull { it.name == "textures" } ?: error("Player has no skin texture") - val json = JsonParser.parseString(String(Base64.getDecoder().decode(texturesProp.value))).asJsonObject + val json = JsonParser.parseString(Base64.decode(texturesProp.value).decodeToString()).asJsonObject val skinUrl = json .getAsJsonObject("textures") From f554d27310ef058548a9f0b365f515f2d5b456ba Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 8 Jan 2026 11:37:14 +0100 Subject: [PATCH 40/64] refactor: replace IOException with more specific exceptions in TreesModule Signed-off-by: Illyrius --- .../kotlin/org/xodium/vanillaplus/modules/TreesModule.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt index 92eb16098..f2a989412 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt @@ -19,8 +19,6 @@ import org.bukkit.event.world.StructureGrowEvent import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.registries.MaterialRegistry -// TODO: replace. -import java.io.IOException import java.nio.channels.Channels import java.nio.channels.ReadableByteChannel import java.nio.file.FileSystems @@ -71,7 +69,7 @@ internal object TreesModule : ModuleInterface { } } } - } catch (e: IOException) { + } catch (e: Exception) { error("Failed to load schematics from $resourceDir: ${e.message}") } } @@ -90,7 +88,7 @@ internal object TreesModule : ModuleInterface { return try { format.getReader(Channels.newInputStream(channel)).read() } catch (e: Exception) { - throw IOException("Failed to read schematic $path: ${e.message}", e) + error("Failed to read schematic $path: ${e.message}") } } From 76d2ccb3db7c74c32d8eb3a8385c8f455ee227d0 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Fri, 9 Jan 2026 07:40:24 +0100 Subject: [PATCH 41/64] refactor: rename InvModule to InventoryModule and update references Signed-off-by: Illyrius --- .idea/modules.xml | 4 +++- src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt | 2 +- .../kotlin/org/xodium/vanillaplus/data/ConfigData.kt | 2 +- .../modules/{InvModule.kt => InventoryModule.kt} | 10 +++++----- 4 files changed, 10 insertions(+), 8 deletions(-) rename src/main/kotlin/org/xodium/vanillaplus/modules/{InvModule.kt => InventoryModule.kt} (93%) diff --git a/.idea/modules.xml b/.idea/modules.xml index 4f712e5b7..457fbe6d8 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,12 +2,14 @@ + + + - \ No newline at end of file diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index fd02efab7..22af802af 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -53,7 +53,7 @@ internal class VanillaPlus : JavaPlugin() { ChatModule, DimensionsModule, EntityModule, - InvModule, + InventoryModule, LocatorModule, MotdModule, OpenableModule, diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index a0fea7d5b..7a8d10da0 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -12,7 +12,7 @@ internal data class ConfigData( var chatModule: ChatModule.Config = ChatModule.Config(), var dimensionsModule: DimensionsModule.Config = DimensionsModule.Config(), var entityModule: EntityModule.Config = EntityModule.Config(), - var invModule: InvModule.Config = InvModule.Config(), + var inventoryModule: InventoryModule.Config = InventoryModule.Config(), var locatorModule: LocatorModule.Config = LocatorModule.Config(), var motdModule: MotdModule.Config = MotdModule.Config(), var openableModule: OpenableModule.Config = OpenableModule.Config(), diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt similarity index 93% rename from src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt rename to src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt index 278f12d53..e84130fe6 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InvModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt @@ -26,8 +26,8 @@ import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils import org.xodium.vanillaplus.utils.Utils.MM -/** Represents a module handling inv mechanics within the system. */ -internal object InvModule : ModuleInterface { +/** Represents a module handling inventory mechanics within the system. */ +internal object InventoryModule : ModuleInterface { override val cmds = listOf( CommandData( @@ -65,7 +65,7 @@ internal object InvModule : ModuleInterface { ?: player.inventory.itemInMainHand.type if (material == Material.AIR) { - player.sendActionBar(MM.deserialize(config.invModule.i18n.noMaterialSpecified)) + player.sendActionBar(MM.deserialize(config.inventoryModule.i18n.noMaterialSpecified)) return 0 } @@ -91,7 +91,7 @@ internal object InvModule : ModuleInterface { if (foundContainers.isEmpty()) { player.sendActionBar( MM.deserialize( - config.invModule.i18n.noMatchingItems, + config.inventoryModule.i18n.noMatchingItems, Placeholder.component("material", MM.deserialize(material.name)), ), ) @@ -100,7 +100,7 @@ internal object InvModule : ModuleInterface { player.sendActionBar( MM.deserialize( - config.invModule.i18n.foundItemsInChests, + config.inventoryModule.i18n.foundItemsInChests, Placeholder.component("material", MM.deserialize(material.name)), ), ) From 2e28bc39ce6c620273e5573054e27c741464cff6 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Fri, 9 Jan 2026 08:07:26 +0100 Subject: [PATCH 42/64] refactor: update material argument handling to use ItemStack instead of Material Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/modules/InventoryModule.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt index e84130fe6..cf280dd95 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt @@ -6,7 +6,6 @@ import com.mojang.brigadier.context.CommandContext import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.command.brigadier.argument.ArgumentTypes -import io.papermc.paper.registry.RegistryKey import kotlinx.serialization.Serializable import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.bukkit.Color @@ -14,6 +13,7 @@ import org.bukkit.Material import org.bukkit.Particle import org.bukkit.block.Block import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault import org.xodium.vanillaplus.VanillaPlus.Companion.instance @@ -36,7 +36,7 @@ internal object InventoryModule : ModuleInterface { .requires { it.sender.hasPermission(perms[0]) } .then( Commands - .argument("material", ArgumentTypes.resource(RegistryKey.ITEM)) + .argument("material", ArgumentTypes.itemStack()) .playerExecuted { _, ctx -> handleSearch(ctx) }, ).executesCatching { handleSearch(it) }, "Search nearby chests for specific items", @@ -61,7 +61,7 @@ internal object InventoryModule : ModuleInterface { private fun handleSearch(ctx: CommandContext): Int { val player = ctx.source.sender as? Player ?: return 0 val material = - runCatching { ctx.getArgument("material", Material::class.java) }.getOrNull() + runCatching { ctx.getArgument("material", ItemStack::class.java).type }.getOrNull() ?: player.inventory.itemInMainHand.type if (material == Material.AIR) { From dffef8217493e73b1ff5c8a1965f16e7fc33d064 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Mon, 12 Jan 2026 12:07:38 +0100 Subject: [PATCH 43/64] refactor: restrict player interaction in SitModule to Survival mode only Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index d46db36d6..504e4a76e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -1,6 +1,7 @@ package org.xodium.vanillaplus.modules import kotlinx.serialization.Serializable +import org.bukkit.GameMode import org.bukkit.Location import org.bukkit.Material import org.bukkit.block.BlockFace @@ -52,6 +53,7 @@ internal object SitModule : ModuleInterface { private fun playerInteract(event: PlayerInteractEvent) { val player = event.player + if (player.gameMode != GameMode.SURVIVAL) return if (event.action != Action.RIGHT_CLICK_BLOCK || player.isSneaking || player.isInsideVehicle) return if (player.inventory.itemInMainHand.type != Material.AIR) return From 3a0f39e764ca3c546368d76c610e72037c13642d Mon Sep 17 00:00:00 2001 From: Illyrius Date: Tue, 13 Jan 2026 07:37:04 +0100 Subject: [PATCH 44/64] refactor: manage occupied blocks for sitting players in SitModule Signed-off-by: Illyrius --- .../xodium/vanillaplus/modules/SitModule.kt | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index 504e4a76e..2922d6e5e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -28,6 +28,7 @@ import kotlin.uuid.toKotlinUuid @OptIn(ExperimentalUuidApi::class) internal object SitModule : ModuleInterface { private val sittingPlayers = mutableMapOf() + private val occupiedBlocks = mutableMapOf() private val blockCenterOffset = Vector(0.5, 0.5, 0.5) private val playerStandUpOffset = Vector(0.0, 0.5, 0.0) @@ -68,8 +69,8 @@ internal object SitModule : ModuleInterface { } if (!isSitTarget) return - if (block.getRelative(BlockFace.UP).type.isCollidable) return + if (occupiedBlocks.containsKey(block.location)) return event.isCancelled = true sit(player, block.location.add(blockCenterOffset)) @@ -83,7 +84,16 @@ internal object SitModule : ModuleInterface { val player = event.entity as? Player ?: return sittingPlayers.remove(player.uniqueId.toKotlinUuid())?.let { armorStand -> + val blockLocation = + armorStand.location + .clone() + .subtract(blockCenterOffset) + .block.location + + occupiedBlocks.remove(blockLocation) + val safe = armorStand.location.clone().add(playerStandUpOffset) + safe.yaw = player.location.yaw safe.pitch = player.location.pitch player.teleport(safe) @@ -96,7 +106,16 @@ internal object SitModule : ModuleInterface { * @param event The [PlayerQuitEvent] triggered when the player leaves the server. */ private fun playerQuit(event: PlayerQuitEvent) { - sittingPlayers.remove(event.player.uniqueId.toKotlinUuid())?.remove() + sittingPlayers.remove(event.player.uniqueId.toKotlinUuid())?.let { armorStand -> + val blockLocation = + armorStand.location + .clone() + .subtract(blockCenterOffset) + .block.location + + occupiedBlocks.remove(blockLocation) + armorStand.remove() + } } /** @@ -105,10 +124,18 @@ internal object SitModule : ModuleInterface { */ private fun entityDamage(event: EntityDamageEvent) { val player = event.entity as? Player ?: return + val playerId = player.uniqueId.toKotlinUuid() + + sittingPlayers[playerId]?.let { stand -> + val blockLocation = + stand.location + .clone() + .subtract(blockCenterOffset) + .block.location - sittingPlayers[player.uniqueId.toKotlinUuid()]?.let { stand -> + occupiedBlocks.remove(blockLocation) stand.removePassenger(player) - sittingPlayers.remove(player.uniqueId.toKotlinUuid()) + sittingPlayers.remove(playerId) } } @@ -123,6 +150,7 @@ internal object SitModule : ModuleInterface { val armorStandBlock = armorStand.location.subtract(blockCenterOffset).block if (armorStandBlock.location == brokenBlockLocation) { + occupiedBlocks.remove(brokenBlockLocation) armorStand.passengers .filterIsInstance() .forEach { player -> armorStand.removePassenger(player) } @@ -144,6 +172,11 @@ internal object SitModule : ModuleInterface { location: Location, ) { val world = location.world ?: return + val blockLocation = + location + .clone() + .subtract(blockCenterOffset) + .block.location val armorStand = world.spawn(location, ArmorStand::class.java) { it.isVisible = false @@ -153,7 +186,11 @@ internal object SitModule : ModuleInterface { } armorStand.addPassenger(player) - sittingPlayers[player.uniqueId.toKotlinUuid()] = armorStand + + val playerId = player.uniqueId.toKotlinUuid() + + sittingPlayers[playerId] = armorStand + occupiedBlocks[blockLocation] = playerId } @Serializable From 61cf190d079625b827f0232d7f2fa22875908509 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Tue, 13 Jan 2026 07:53:12 +0100 Subject: [PATCH 45/64] refactor: add reload command and permission to ConfigData for dynamic configuration management Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 11 ++- .../org/xodium/vanillaplus/data/ConfigData.kt | 79 +++++++++++++++++- .../vanillaplus/managers/ConfigManager.kt | 83 ------------------- 3 files changed, 83 insertions(+), 90 deletions(-) delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 22af802af..81611b7b2 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -5,9 +5,8 @@ package org.xodium.vanillaplus import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents import org.bukkit.plugin.java.JavaPlugin import org.xodium.vanillaplus.data.ConfigData +import org.xodium.vanillaplus.data.ConfigData.Companion.load import org.xodium.vanillaplus.hooks.WorldEditHook -import org.xodium.vanillaplus.managers.ConfigManager -import org.xodium.vanillaplus.managers.ConfigManager.load import org.xodium.vanillaplus.modules.* import org.xodium.vanillaplus.recipes.RottenFleshRecipe import org.xodium.vanillaplus.recipes.WoodLogRecipe @@ -34,12 +33,12 @@ internal class VanillaPlus : JavaPlugin() { instance.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { event -> event.registrar().register( - ConfigManager.reloadCommand.builder.build(), - ConfigManager.reloadCommand.description, - ConfigManager.reloadCommand.aliases, + ConfigData.reloadCommand.builder.build(), + ConfigData.reloadCommand.description, + ConfigData.reloadCommand.aliases, ) } - instance.server.pluginManager.addPermission(ConfigManager.reloadPermission) + instance.server.pluginManager.addPermission(ConfigData.reloadPermission) configData = ConfigData().load("config.json") diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index 7a8d10da0..82b2b98f1 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -1,9 +1,24 @@ @file:Suppress("ktlint:standard:no-wildcard-imports") +@file:OptIn(ExperimentalSerializationApi::class) package org.xodium.vanillaplus.data +import io.papermc.paper.command.brigadier.Commands +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.bukkit.entity.Player +import org.bukkit.permissions.Permission +import org.bukkit.permissions.PermissionDefault +import org.xodium.vanillaplus.VanillaPlus.Companion.configData +import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.modules.* +import org.xodium.vanillaplus.strategies.CapitalizedStrategy +import org.xodium.vanillaplus.utils.CommandUtils.executesCatching +import org.xodium.vanillaplus.utils.Utils.MM +import org.xodium.vanillaplus.utils.Utils.prefix +import kotlin.io.path.* +import kotlin.time.measureTime /** Configuration data for the plugin. */ @Serializable @@ -23,4 +38,66 @@ internal data class ConfigData( var tabListModule: TabListModule.Config = TabListModule.Config(), var tameableModule: TameableModule.Config = TameableModule.Config(), var treesModule: TreesModule.Config = TreesModule.Config(), -) +) { + companion object { + private val json = + Json { + prettyPrint = true + encodeDefaults = true + ignoreUnknownKeys = true + namingStrategy = CapitalizedStrategy + } + + val reloadCommand: CommandData = + CommandData( + Commands + .literal("vanillaplus") + .requires { it.sender.hasPermission(reloadPermission) } + .then( + Commands + .literal("reload") + .executesCatching { + configData = ConfigData().load("config.json") + + if (it.source.sender is Player) { + it.source.sender.sendMessage( + MM.deserialize("${instance.prefix} configuration reloaded!"), + ) + } else { + instance.logger.info("Configuration reloaded!") + } + }, + ), + "Allows to plugin specific admin commands", + listOf("vp"), + ) + + val reloadPermission: Permission = + Permission( + "${instance.javaClass.simpleName}.reload".lowercase(), + "Allows use of the reload command", + PermissionDefault.OP, + ) + + /** + * Loads or creates the configuration file. + * @param fileName The name of the configuration file. + * @return The loaded configuration data. + */ + inline fun T.load(fileName: String): T { + val file = instance.dataFolder.toPath() / fileName + + if (!instance.dataFolder.toPath().exists()) instance.dataFolder.toPath().createDirectories() + + val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else this + + instance.logger.info( + "${if (file.exists()) "Loaded configuration from $fileName" else "Created default $fileName"} | Took ${ + measureTime { file.writeText(json.encodeToString(loadedConfig)) }.inWholeMilliseconds + }ms", + ) + + return loadedConfig + } + } +} diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt deleted file mode 100644 index 3b0be7848..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/managers/ConfigManager.kt +++ /dev/null @@ -1,83 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) - -@file:Suppress("ktlint:standard:no-wildcard-imports") - -package org.xodium.vanillaplus.managers - -import io.papermc.paper.command.brigadier.Commands -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import org.bukkit.entity.Player -import org.bukkit.permissions.Permission -import org.bukkit.permissions.PermissionDefault -import org.xodium.vanillaplus.VanillaPlus.Companion.configData -import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.data.CommandData -import org.xodium.vanillaplus.data.ConfigData -import org.xodium.vanillaplus.strategies.CapitalizedStrategy -import org.xodium.vanillaplus.utils.CommandUtils.executesCatching -import org.xodium.vanillaplus.utils.Utils.MM -import org.xodium.vanillaplus.utils.Utils.prefix -import kotlin.io.path.* -import kotlin.time.measureTime - -/** Manages loading and saving the configuration file. */ -internal object ConfigManager { - private val json = - Json { - prettyPrint = true - encodeDefaults = true - ignoreUnknownKeys = true - namingStrategy = CapitalizedStrategy - } - - val reloadCommand: CommandData = - CommandData( - Commands - .literal("vanillaplus") - .requires { it.sender.hasPermission(reloadPermission) } - .then( - Commands - .literal("reload") - .executesCatching { - if (it.source.sender !is Player) { - instance.logger.warning("Command can only be executed by a Player!") - } - configData = ConfigData().load("config.json") - it.source.sender.sendMessage( - MM.deserialize("${instance.prefix} configuration reloaded!"), - ) - }, - ), - "Allows to plugin specific admin commands", - listOf("vp"), - ) - - val reloadPermission: Permission = - Permission( - "${instance.javaClass.simpleName}.reload".lowercase(), - "Allows use of the reload command", - PermissionDefault.OP, - ) - - /** - * Loads or creates the configuration file. - * @param fileName The name of the configuration file. - * @return The loaded configuration data. - */ - inline fun T.load(fileName: String): T { - val file = instance.dataFolder.toPath() / fileName - - if (!instance.dataFolder.toPath().exists()) instance.dataFolder.toPath().createDirectories() - - val loadedConfig = if (file.exists()) json.decodeFromString(file.readText()) else this - - instance.logger.info( - "${if (file.exists()) "Loaded configuration from $fileName" else "Created default $fileName"} | Took ${ - measureTime { file.writeText(json.encodeToString(loadedConfig)) }.inWholeMilliseconds - }ms", - ) - - return loadedConfig - } -} From 33bd15076d7b1b1ec8150f12269c6bda49cd03db Mon Sep 17 00:00:00 2001 From: Illyrius Date: Tue, 13 Jan 2026 08:01:14 +0100 Subject: [PATCH 46/64] removing custom trees until i find a better solution Signed-off-by: Illyrius --- build.gradle.kts | 6 - .../org/xodium/vanillaplus/VanillaPlus.kt | 2 - .../org/xodium/vanillaplus/data/ConfigData.kt | 1 - .../xodium/vanillaplus/hooks/WorldEditHook.kt | 21 -- .../xodium/vanillaplus/modules/TreesModule.kt | 223 ------------------ .../trees/acacia/AcaciaTree_01.schem | Bin 526 -> 0 bytes .../trees/acacia/AcaciaTree_02.schem | Bin 538 -> 0 bytes .../trees/acacia/AcaciaTree_03.schem | Bin 609 -> 0 bytes .../trees/acacia/AcaciaTree_04.schem | Bin 599 -> 0 bytes .../trees/acacia/AcaciaTree_05.schem | Bin 723 -> 0 bytes .../trees/acacia/AcaciaTree_06.schem | Bin 920 -> 0 bytes .../trees/acacia/AcaciaTree_07.schem | Bin 867 -> 0 bytes .../trees/acacia/AcaciaTree_08.schem | Bin 997 -> 0 bytes .../trees/acacia/AcaciaTree_09.schem | Bin 1008 -> 0 bytes .../trees/acacia/AcaciaTree_10.schem | Bin 1051 -> 0 bytes .../schematics/trees/birch/BirchTree_01.schem | Bin 611 -> 0 bytes .../schematics/trees/birch/BirchTree_02.schem | Bin 686 -> 0 bytes .../schematics/trees/birch/BirchTree_03.schem | Bin 642 -> 0 bytes .../schematics/trees/birch/BirchTree_04.schem | Bin 744 -> 0 bytes .../schematics/trees/birch/BirchTree_05.schem | Bin 567 -> 0 bytes .../schematics/trees/birch/BirchTree_06.schem | Bin 822 -> 0 bytes .../schematics/trees/birch/BirchTree_07.schem | Bin 871 -> 0 bytes .../schematics/trees/birch/BirchTree_08.schem | Bin 836 -> 0 bytes .../schematics/trees/birch/BirchTree_09.schem | Bin 951 -> 0 bytes .../schematics/trees/birch/BirchTree_10.schem | Bin 939 -> 0 bytes .../trees/cherry/CherryTree_01.schem | Bin 600 -> 0 bytes .../trees/cherry/CherryTree_02.schem | Bin 737 -> 0 bytes .../trees/cherry/CherryTree_03.schem | Bin 876 -> 0 bytes .../trees/cherry/CherryTree_04.schem | Bin 994 -> 0 bytes .../trees/cherry/CherryTree_05.schem | Bin 1200 -> 0 bytes .../trees/cherry/CherryTree_06.schem | Bin 1131 -> 0 bytes .../trees/cherry/CherryTree_07.schem | Bin 1327 -> 0 bytes .../trees/cherry/CherryTree_08.schem | Bin 1321 -> 0 bytes .../trees/cherry/CherryTree_09.schem | Bin 1591 -> 0 bytes .../trees/cherry/CherryTree_10.schem | Bin 1423 -> 0 bytes .../trees/crimson/CrimsonTree_01.schem | Bin 513 -> 0 bytes .../trees/crimson/CrimsonTree_02.schem | Bin 482 -> 0 bytes .../trees/crimson/CrimsonTree_03.schem | Bin 524 -> 0 bytes .../trees/crimson/CrimsonTree_04.schem | Bin 591 -> 0 bytes .../trees/crimson/CrimsonTree_05.schem | Bin 629 -> 0 bytes .../trees/crimson/CrimsonTree_06.schem | Bin 619 -> 0 bytes .../trees/crimson/CrimsonTree_07.schem | Bin 638 -> 0 bytes .../trees/crimson/CrimsonTree_08.schem | Bin 692 -> 0 bytes .../trees/crimson/CrimsonTree_09.schem | Bin 742 -> 0 bytes .../trees/crimson/CrimsonTree_10.schem | Bin 837 -> 0 bytes .../trees/dark_oak/DarkOakTree_01.schem | Bin 730 -> 0 bytes .../trees/dark_oak/DarkOakTree_02.schem | Bin 719 -> 0 bytes .../trees/dark_oak/DarkOakTree_03.schem | Bin 828 -> 0 bytes .../trees/dark_oak/DarkOakTree_04.schem | Bin 906 -> 0 bytes .../trees/dark_oak/DarkOakTree_05.schem | Bin 1250 -> 0 bytes .../trees/dark_oak/DarkOakTree_06.schem | Bin 1427 -> 0 bytes .../trees/dark_oak/DarkOakTree_07.schem | Bin 1705 -> 0 bytes .../trees/dark_oak/DarkOakTree_08.schem | Bin 1803 -> 0 bytes .../trees/dark_oak/DarkOakTree_09.schem | Bin 1685 -> 0 bytes .../trees/dark_oak/DarkOakTree_10.schem | Bin 1632 -> 0 bytes .../trees/jungle/JungleTree_01.schem | Bin 603 -> 0 bytes .../trees/jungle/JungleTree_02.schem | Bin 656 -> 0 bytes .../trees/jungle/JungleTree_03.schem | Bin 809 -> 0 bytes .../trees/jungle/JungleTree_04.schem | Bin 960 -> 0 bytes .../trees/jungle/JungleTree_05.schem | Bin 1044 -> 0 bytes .../trees/jungle/JungleTree_06.schem | Bin 1047 -> 0 bytes .../trees/jungle/JungleTree_07.schem | Bin 1254 -> 0 bytes .../trees/jungle/JungleTree_08.schem | Bin 1390 -> 0 bytes .../trees/jungle/JungleTree_09.schem | Bin 1217 -> 0 bytes .../trees/jungle/JungleTree_10.schem | Bin 1160 -> 0 bytes .../trees/mangrove/MangroveTree_01.schem | Bin 654 -> 0 bytes .../trees/mangrove/MangroveTree_02.schem | Bin 914 -> 0 bytes .../trees/mangrove/MangroveTree_03.schem | Bin 959 -> 0 bytes .../trees/mangrove/MangroveTree_04.schem | Bin 1067 -> 0 bytes .../trees/mangrove/MangroveTree_05.schem | Bin 1176 -> 0 bytes .../trees/mangrove/MangroveTree_06.schem | Bin 1128 -> 0 bytes .../trees/mangrove/MangroveTree_07.schem | Bin 1205 -> 0 bytes .../trees/mangrove/MangroveTree_08.schem | Bin 1232 -> 0 bytes .../trees/mangrove/MangroveTree_09.schem | Bin 1443 -> 0 bytes .../trees/mangrove/MangroveTree_10.schem | Bin 1610 -> 0 bytes .../schematics/trees/oak/OakTree_01.schem | Bin 760 -> 0 bytes .../schematics/trees/oak/OakTree_02.schem | Bin 836 -> 0 bytes .../schematics/trees/oak/OakTree_03.schem | Bin 1161 -> 0 bytes .../schematics/trees/oak/OakTree_04.schem | Bin 1301 -> 0 bytes .../schematics/trees/oak/OakTree_05.schem | Bin 1249 -> 0 bytes .../schematics/trees/oak/OakTree_06.schem | Bin 1632 -> 0 bytes .../schematics/trees/oak/OakTree_07.schem | Bin 1543 -> 0 bytes .../schematics/trees/oak/OakTree_08.schem | Bin 2123 -> 0 bytes .../schematics/trees/oak/OakTree_09.schem | Bin 1938 -> 0 bytes .../schematics/trees/oak/OakTree_10.schem | Bin 2013 -> 0 bytes .../trees/pale_oak/PaleOakTree_01.schem | Bin 456 -> 0 bytes .../trees/pale_oak/PaleOakTree_02.schem | Bin 576 -> 0 bytes .../trees/pale_oak/PaleOakTree_03.schem | Bin 646 -> 0 bytes .../trees/pale_oak/PaleOakTree_04.schem | Bin 746 -> 0 bytes .../trees/pale_oak/PaleOakTree_05.schem | Bin 784 -> 0 bytes .../trees/pale_oak/PaleOakTree_06.schem | Bin 1011 -> 0 bytes .../trees/pale_oak/PaleOakTree_07.schem | Bin 1062 -> 0 bytes .../trees/pale_oak/PaleOakTree_08.schem | Bin 1118 -> 0 bytes .../trees/pale_oak/PaleOakTree_09.schem | Bin 1278 -> 0 bytes .../trees/pale_oak/PaleOakTree_10.schem | Bin 1462 -> 0 bytes .../trees/spruce/SpruceTree_01.schem | Bin 603 -> 0 bytes .../trees/spruce/SpruceTree_02.schem | Bin 666 -> 0 bytes .../trees/spruce/SpruceTree_03.schem | Bin 685 -> 0 bytes .../trees/spruce/SpruceTree_04.schem | Bin 715 -> 0 bytes .../trees/spruce/SpruceTree_05.schem | Bin 765 -> 0 bytes .../trees/spruce/SpruceTree_06.schem | Bin 872 -> 0 bytes .../trees/spruce/SpruceTree_07.schem | Bin 872 -> 0 bytes .../trees/spruce/SpruceTree_08.schem | Bin 1072 -> 0 bytes .../trees/spruce/SpruceTree_09.schem | Bin 1122 -> 0 bytes .../trees/spruce/SpruceTree_10.schem | Bin 791 -> 0 bytes .../trees/warped/WarpedTree_01.schem | Bin 386 -> 0 bytes .../trees/warped/WarpedTree_02.schem | Bin 404 -> 0 bytes .../trees/warped/WarpedTree_03.schem | Bin 505 -> 0 bytes .../trees/warped/WarpedTree_04.schem | Bin 563 -> 0 bytes .../trees/warped/WarpedTree_05.schem | Bin 632 -> 0 bytes .../trees/warped/WarpedTree_06.schem | Bin 717 -> 0 bytes .../trees/warped/WarpedTree_07.schem | Bin 789 -> 0 bytes .../trees/warped/WarpedTree_08.schem | Bin 841 -> 0 bytes .../trees/warped/WarpedTree_09.schem | Bin 837 -> 0 bytes .../trees/warped/WarpedTree_10.schem | Bin 848 -> 0 bytes 115 files changed, 253 deletions(-) delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/hooks/WorldEditHook.kt delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_01.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_02.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_03.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_04.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_05.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_06.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_07.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_08.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_09.schem delete mode 100644 src/main/resources/schematics/trees/acacia/AcaciaTree_10.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_01.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_02.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_03.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_04.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_05.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_06.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_07.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_08.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_09.schem delete mode 100644 src/main/resources/schematics/trees/birch/BirchTree_10.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_01.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_02.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_03.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_04.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_05.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_06.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_07.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_08.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_09.schem delete mode 100644 src/main/resources/schematics/trees/cherry/CherryTree_10.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_01.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_02.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_03.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_04.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_05.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_06.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_07.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_08.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_09.schem delete mode 100644 src/main/resources/schematics/trees/crimson/CrimsonTree_10.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_01.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_02.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_03.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_04.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_05.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_06.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_07.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_08.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_09.schem delete mode 100644 src/main/resources/schematics/trees/dark_oak/DarkOakTree_10.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_01.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_02.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_03.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_04.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_05.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_06.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_07.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_08.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_09.schem delete mode 100644 src/main/resources/schematics/trees/jungle/JungleTree_10.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_01.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_02.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_03.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_04.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_05.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_06.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_07.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_08.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_09.schem delete mode 100644 src/main/resources/schematics/trees/mangrove/MangroveTree_10.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_01.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_02.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_03.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_04.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_05.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_06.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_07.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_08.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_09.schem delete mode 100644 src/main/resources/schematics/trees/oak/OakTree_10.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_01.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_02.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_03.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_04.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_05.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_06.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_07.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_08.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_09.schem delete mode 100644 src/main/resources/schematics/trees/pale_oak/PaleOakTree_10.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_01.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_02.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_03.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_04.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_05.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_06.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_07.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_08.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_09.schem delete mode 100644 src/main/resources/schematics/trees/spruce/SpruceTree_10.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_01.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_02.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_03.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_04.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_05.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_06.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_07.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_08.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_09.schem delete mode 100644 src/main/resources/schematics/trees/warped/WarpedTree_10.schem diff --git a/build.gradle.kts b/build.gradle.kts index 88f727f99..53a378242 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import xyz.jpenilla.resourcefactory.paper.PaperPluginYaml import xyz.jpenilla.runtask.task.AbstractRun plugins { @@ -20,12 +19,10 @@ description = "Minecraft plugin that enhances the base gameplay" repositories { mavenCentral() maven("https://repo.papermc.io/repository/maven-public/") - maven("https://maven.enginehub.org/repo/") } dependencies { compileOnly("io.papermc.paper:paper-api:$version-R0.1-SNAPSHOT") - compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.18") // TODO("Move away from WorldEdit") implementation(kotlin("stdlib")) implementation("org.jetbrains.kotlin:kotlin-reflect") @@ -60,7 +57,4 @@ paperPluginYaml { 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/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 81611b7b2..29ed4ecc4 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -6,7 +6,6 @@ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents import org.bukkit.plugin.java.JavaPlugin import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.data.ConfigData.Companion.load -import org.xodium.vanillaplus.hooks.WorldEditHook import org.xodium.vanillaplus.modules.* import org.xodium.vanillaplus.recipes.RottenFleshRecipe import org.xodium.vanillaplus.recipes.WoodLogRecipe @@ -62,7 +61,6 @@ internal class VanillaPlus : JavaPlugin() { SitModule, TabListModule, TameableModule, - if (WorldEditHook.get()) TreesModule else null, ).forEach { module -> module.register() } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index 82b2b98f1..e2a7b8c92 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -37,7 +37,6 @@ internal data class ConfigData( var sitModule: SitModule.Config = SitModule.Config(), var tabListModule: TabListModule.Config = TabListModule.Config(), var tameableModule: TameableModule.Config = TameableModule.Config(), - var treesModule: TreesModule.Config = TreesModule.Config(), ) { companion object { private val json = diff --git a/src/main/kotlin/org/xodium/vanillaplus/hooks/WorldEditHook.kt b/src/main/kotlin/org/xodium/vanillaplus/hooks/WorldEditHook.kt deleted file mode 100644 index 1cf8ec50b..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/hooks/WorldEditHook.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.xodium.vanillaplus.hooks - -import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.modules.TreesModule - -/** A utility object for checking plugin availability and handling related dependencies. */ -internal object WorldEditHook { - /** - * Checks if a specified plugin is available and optionally logs a warning if not found. - * @return true if the plugin is installed and enabled, false otherwise. - */ - fun get(): Boolean { - val plugin = instance.server.pluginManager.getPlugin("WorldEdit") != null - - if (!plugin) { - instance.logger.warning("FAWE or WorldEdit not found, disabling ${TreesModule.javaClass.simpleName}") - } - - return plugin - } -} diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt deleted file mode 100644 index f2a989412..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TreesModule.kt +++ /dev/null @@ -1,223 +0,0 @@ -package org.xodium.vanillaplus.modules - -import com.sk89q.worldedit.WorldEdit -import com.sk89q.worldedit.bukkit.BukkitAdapter -import com.sk89q.worldedit.extent.clipboard.Clipboard -import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats -import com.sk89q.worldedit.function.mask.BlockTypeMask -import com.sk89q.worldedit.function.operation.Operations -import com.sk89q.worldedit.math.BlockVector3 -import com.sk89q.worldedit.math.transform.AffineTransform -import com.sk89q.worldedit.session.ClipboardHolder -import kotlinx.serialization.Serializable -import org.bukkit.Material -import org.bukkit.Tag -import org.bukkit.block.Block -import org.bukkit.event.EventHandler -import org.bukkit.event.EventPriority -import org.bukkit.event.world.StructureGrowEvent -import org.xodium.vanillaplus.VanillaPlus.Companion.instance -import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.registries.MaterialRegistry -import java.nio.channels.Channels -import java.nio.channels.ReadableByteChannel -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardOpenOption -import java.util.stream.Collectors - -/** Represents a module handling tree mechanics within the system. */ -internal object TreesModule : ModuleInterface { - private val schematicCache: Map> by lazy { - MaterialRegistry.SAPLING_LINKS.mapValues { loadSchematics("/schematics/${it.value}") } - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: StructureGrowEvent) = structureGrow(event) - - /** - * Handles StructureGrowEvent and attempts to paste a schematic - * when the grown block is a sapling or fungus. - * @param event The [StructureGrowEvent] triggered by natural growth. - */ - private fun structureGrow(event: StructureGrowEvent) { - event.location.block - .takeIf { - Tag.SAPLINGS.isTagged(it.type) || - it.type == Material.WARPED_FUNGUS || - it.type == Material.CRIMSON_FUNGUS - }?.let { event.isCancelled = pasteSchematic(it) } - } - - /** - * Load schematics from the specified resource directory. - * @param resourceDir The directory containing the schematics. - * @return A list of loaded schematics. - */ - private fun loadSchematics(resourceDir: String): List { - val url = javaClass.getResource(resourceDir) ?: error("Resource directory not found: $resourceDir") - return try { - FileSystems.newFileSystem(url.toURI(), mapOf("create" to false)).use { fs -> - Files - .walk(fs.getPath(resourceDir.removePrefix("/")), 1) - .filter { Files.isRegularFile(it) } - .collect(Collectors.toList()) - .map { path -> - Files.newByteChannel(path, StandardOpenOption.READ).use { channel -> - readClipboard(path, channel) - } - } - } - } catch (e: Exception) { - error("Failed to load schematics from $resourceDir: ${e.message}") - } - } - - /** - * Read a schematic from the specified path. - * @param path The path to the schematic file. - * @param channel The channel to read the schematic from. - * @return The loaded schematic. - */ - private fun readClipboard( - path: Path, - channel: ReadableByteChannel, - ): Clipboard { - val format = ClipboardFormats.findByAlias("schem") ?: error("Unsupported schematic format for resource: $path") - return try { - format.getReader(Channels.newInputStream(channel)).read() - } catch (e: Exception) { - error("Failed to read schematic $path: ${e.message}") - } - } - - /** - * Paste a random schematic at the specified block. - * @param block The block to paste the schematic at. - * @return True if the schematic was pasted successfully. - */ - private fun pasteSchematic(block: Block): Boolean { - val clipboards = schematicCache[block.type] ?: return false - return pasteSchematic(block, clipboards.random()) - } - - /** - * Paste a schematic at the specified block. - * @param block The block to paste the schematic at. - * @param clipboard The specific clipboard to paste. - * @return True if the schematic was pasted successfully. - */ - private fun pasteSchematic( - block: Block, - clipboard: Clipboard, - ): Boolean { - instance.server.scheduler.runTask( - instance, - Runnable { - try { - WorldEdit - .getInstance() - .newEditSession(BukkitAdapter.adapt(block.world)) - .use { editSession -> - block.type = Material.AIR - editSession.mask = - BlockTypeMask( - editSession, - config.treesModule.treeMask.map { BukkitAdapter.asBlockType(it) }, - ) - ClipboardHolder(clipboard).apply { - transform = transform.combine(AffineTransform().rotateY(getRandomRotation().toDouble())) - Operations.complete( - createPaste(editSession) - .to(BlockVector3.at(block.x, block.y, block.z)) - .copyBiomes(config.treesModule.copyBiomes) - .copyEntities(config.treesModule.copyEntities) - .ignoreAirBlocks(config.treesModule.ignoreAirBlocks) - .ignoreStructureVoidBlocks(config.treesModule.ignoreStructureVoidBlocks) - .build(), - ) - } - } - } catch (e: Exception) { - instance.logger.severe("Error while pasting schematic: ${e.message}") - } - }, - ) - return true - } - - /** - * Returns a random rotation angle from a given list of angles. - * @param angle The list of angles to choose from. Defaults to [0, 90, 180, 270]. - * @return A random angle from the provided or default list. - * @throws IllegalArgumentException if any angle is not a multiple of 90 or outside [0, 270) - */ - private fun getRandomRotation(angle: List = listOf(0, 90, 180, 270)): Int { - require(angle.all { it in setOf(0, 90, 180, 270) }) { "Angles must be one of: 0, 90, 180, 270" } - return angle.random() - } - - @Serializable - data class Config( - var enabled: Boolean = true, - var copyBiomes: Boolean = false, - var copyEntities: Boolean = false, - var ignoreAirBlocks: Boolean = true, - var ignoreStructureVoidBlocks: Boolean = true, - var treeMask: Set = - setOf( - Material.AZALEA, - Material.WEEPING_VINES, - Material.CORNFLOWER, - Material.CLOSED_EYEBLOSSOM, - Material.PINK_TULIP, - Material.OPEN_EYEBLOSSOM, - Material.WHITE_TULIP, - Material.SNOW, - Material.FERN, - Material.AZALEA_LEAVES, - Material.SUNFLOWER, - Material.PEONY, - Material.PINK_PETALS, - Material.LILAC, - Material.LARGE_FERN, - Material.VINE, - Material.CAVE_VINES_PLANT, - Material.TORCHFLOWER, - Material.RED_TULIP, - Material.ORANGE_TULIP, - Material.KELP, - Material.AIR, - Material.FLOWERING_AZALEA, - Material.AZURE_BLUET, - Material.MOSS_BLOCK, - Material.PITCHER_PLANT, - Material.WEEPING_VINES_PLANT, - Material.TALL_SEAGRASS, - Material.TWISTING_VINES, - Material.BLUE_ORCHID, - Material.CAVE_VINES, - Material.ROSE_BUSH, - Material.SPORE_BLOSSOM, - Material.FLOWERING_AZALEA_LEAVES, - Material.POPPY, - Material.TWISTING_VINES_PLANT, - Material.DANDELION, - Material.DEAD_BUSH, - Material.LILY_OF_THE_VALLEY, - Material.KELP_PLANT, - Material.SHORT_GRASS, - Material.CHORUS_FLOWER, - Material.ALLIUM, - Material.MANGROVE_PROPAGULE, - Material.CHERRY_LEAVES, - Material.SUGAR_CANE, - Material.SEAGRASS, - Material.MOSS_CARPET, - Material.WITHER_ROSE, - Material.TALL_GRASS, - Material.OXEYE_DAISY, - ), - ) -} diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_01.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_01.schem deleted file mode 100644 index 45d8146b38ad1196ea3f9d0fcd25d45c8ac2c68a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmV+p0`dJHiwFP!00000|Ls@HZqq;%z0QoC)KO_x?Dztbs-falDVvH&EPz%*w2LT0 z6Z_g8#qm_034t}gz$fulY{DHUrHw5}R#jSZB+KJFbI+V}Y{?oQz=yq$L4p%uWWb<) zfw`6{0|3UL`9>J=r_vhw(0GqVgjD6gX%YdPAKzv0f>NPoRZaEHg}|qm$8eJeH;j-dQfGW+l+&&FAx<_D)+|cWJ-c z*8u)OOb`xEi^tAc6v;q}^p={(t7@JGqt{XR{1uTy4cto^ngm)k@ZO?~69Y{ezN2ky z)vAgjjmD}XMZav5z5C#urYabdr24r?(HLZ~KOt#BE+TUxWDbRsC%0;WAZ4FtN`(XQ zRqEciAuVwjTvXOXm<2dMp-nFmsm6Aua+55_T1{ze@+r1w^e*yL#W9A<$r)bvSn?W! zeuX?m@dfoDl-dZ|rgz-FvPo%!nfVLtaPeWR=X$mo7vF@dyBU|u;M2bXEIJqY6~je* z%e2>nwbyS)YH=cl`YA6*@DF-SdaR6EgsXasy z+Sp@ziDOsZ4TUqWz+>>Z@Xgoh&a@(vNs5u_N=@?f8nmMUm!ljNwfLsb(?M z&nGYD37Z?HHk^kz=g? zM_`6I$F^)J2cWb8jE0nB6gkYKU01lJndh>S6Pns);4fax1F^<_SHLp^%&_K~30rKj cF4%$H#bs=eDY|7M-N}~t15>JE5$6s708V2Fpa1{> diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_03.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_03.schem deleted file mode 100644 index ef95581e743e8e93dc264be4ca4db8fce174da4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmV-n0-pUJiwFP!00000|K(TBZqq;z9>-qCNmQB>H=cl`YAC2sDVK^!9Dr6rY7bF` zHugAK#j%mSY2ga5zyopMd3XRWVb)Q8yn)yaC{fEumUqTG-+bThtfj32RH5?D9pH#d z;hIpsz&H_62mq!DwU=D-d8B?-1?LS)?oreNi#{TN@%78+AKwV&GKvGQ;|b|3P#zo~ z938X{n?1^E9=06VR})0oKM%O{qj&^$4ErL)!MJzQ8xMy<*1$fC1;rEvdhE$$@?E{D z#4IbRuI4#lz2zf>{nONKp839T1rL_kJj}9r?Dk&x-m~Wf4kcih!jl82m%x67qCb#O zqv2~RqoRtH?86?9(7}heWA-&I6PZP1qX|H8l||rhsBUCdb`LC;rN9h5!1N=dBd$YWX@z;o vRIjCXK?Qbbx*T2Z?Jak~cB=RPu?p;^H@!|M=?aT<;hWko6#K6!L=OM}LRcwF diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_04.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_04.schem deleted file mode 100644 index 7e110b05d26c5486861d6599629ed1182d9e062a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 599 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 diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_07.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_07.schem deleted file mode 100644 index 6b4452a841011d46a6bcd4e5b2c78b7bcaa07b1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 zcmV-p1DyOHiwFP!00000|Lxt+PuxZn!10H7SsQN%IptOn$PeX*#03?phqNG} z!U3UZ@Q!ya%sR4%keqw%vB&;7z4agJjgB|VFTu{3Y)C@%{YYqypZ)yo^&1Zh60t}u zbDPOPA1IS0EeW@DS!KmgL|QHB{;Z5TU2|_YmgrY)RL`z$OWOvu$jo1V{JHtB&9YsT zd2g+kndmIb-RBRVJY0UXv}gBPdbAwLLcKvY>~G}Cq(ylk_w=xz4fWt~@73Pn{(fe< z(pfJvyRI(5enrQ}$N$u?_7Ep~Rq^HhB5D7o4zw(+j=o)5Ptz>PRDQ0S`xmSEDcO6O z_MW}4$t954?W|`8@*t4TFFNZFjQnU9U)#gfYwN1gw9=-o%2xDWy;J=fOJ_ANl6_lJ zxS?`wjBd&Nfh{d5RccAQk7SFaafdQFkY^TL%VFs zL%n!puXC9f{l4yvgGc&gpIw`KrX@dIN`6!ny&d&Ft5!bL+XSCj!1#DqS5Q~C`|(cs z-IaWF79Vqu78|5KJ-N0g_xZw`CO@fr)^kzL)!q1-oEF|`$=aplxmNFVwbRQgqwHZ; z9xuMF@6F2Sp_%Yz*SW$bxt5#dimq`dT>IUcYf|WwW`)L0ay@O9tK0kzy>|QEX;SC2 zW_8X>I2u=#$lr2X%y{BK;0Xso%N=$lXuHEU!K^b3;6{1--xqgo;MpC(H>^SbdlG!c zVgP(mQ};iBTg%tpZTcVhe)*>Nu1^O6+-~mv?=b%dZZYrg2Ypdr|F;<+fEyAt1(;OX zyypVi4V+=%o93OEG@m*6o{={<2bt!up8f^^000000000000000000000000000000 t0000000000000000Kk_tC-bX$k?gMxO=dD(o&1Na{{c%?`jWMw006K~vi$%6 diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_08.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_08.schem deleted file mode 100644 index 276ec0e604d48dc0f4ed55a7aec6503ba79f3c10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 997 zcmV84>8QLa5 z&}1--;7lDQ!n9bB=enIbbne!%e@_0VjP4!v!%n38;WBp8=KBF0D}20nkJLSqmIwEV z`qX=#OvEXdSrStBg($16m=aMK(%?HT`EtwVQJ>fkLh_N?+NQP&3Zm9ufBd=gyUOyi zDD%<5D3k0mOZ;f({?6{b?PE1+``#|2q23{zj-TaRrbT&5n_@c7rebn-{P_6nO3towN00xzIqoI7q9hqHN|37)n6UQsQn{96*Szh|GWJ(O|vB9`ITxud%K!1ljCpG z(SvVPatdhUWj2x%x*JgEiO9wiNnfa!->Yf#R$Wz^Rzm8kR7G#}ApN~ho&CH>PE<+V zXFL~DijbmHRa#Q=R6gceN%fKZbY)w@Rb{VIF-^pw;Fa8y<(b%;7Nwl*r97|1R#lv- z*Y)Oeb(+h(7>~th9y}LnNd59irEZ1v`AY8dq8J_WU$biOH@!~~(hJ-Cv%ISX%;#HQ zfv)aowot4GcQtu^MAyjOzD91}Gq=dS;s2)imc4vaI@dVi*G}FI>0l*!F8D8^Iviz{ z=iRqq`*z$3>;(Y8&2jd>E8Z}Hs{jD_U~azu0pO$M?tk;g zgFXoW;N#_de0M$oyth&S;FbmU26leF-+iyu0KhF$%^fgvxEJVO@tc1F=+CYmmJD+P za|VD~!`*S-GnN)@c=Ke-)baZ8A|A8V299DzEMDx%-pw!^ zL~$H1DqMGc7(`LDJi2vfsNQ6II?;u#O#Jo~vH8j~E7F$T@y5fa!McRjH%D#0xYjhs zJUnX()A(o+FwM_`4QpvJvp0_mvyWDnhvg3Ijxg?C-D>FX*gi+x3T^%EIzoL4t@$Iz zdux8~0hqHdF*2_~$KrIhuj#^bxr062kmLH`Ixk)t9wFKn{=1y*i{%Xf0000000000 z000000000000000000000000000000000000000000000000000Jp10(SBYeCkIoR T$xKv>zohvO1!C9|siFV?PmT38 diff --git a/src/main/resources/schematics/trees/acacia/AcaciaTree_09.schem b/src/main/resources/schematics/trees/acacia/AcaciaTree_09.schem deleted file mode 100644 index b38ba9b72eb699dfae6233bf77f5d267b0dd9d13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1008 zcmVkFLpGDgLKRt`!l$xsifT?o{XQk%2qn(0+&YLU`c5h_(N_mF&_PKQ{Pp`EFaOk8 zo>g@@*_#w9`a8?`{_VTBx9@Bn>QP&Fwjf<5H-+s*mZGoKK6noShs#I6OHz zDpW$lM|GjEnG*D$(dp^wEAv@Tu^83F7so|3_+A`K+SqOXy7ef}i>wgkxoSRox0=tg z!>{wny{~j~>e1S>VxngBNsor#%3?ZG^r?RNP)}p7nyT`=k;+u1D|&4Pna?o|cgrd} z(j|qDMJbh%eM*mYX<03D^*|IgwO`4{=dNW!SN7BMs+#PJUyEkvw*!4k(5I&t?#bmm zllxLMYDd*4a&unQYPOS$vXPrjb#nIlx&B_%Wi_43$>NICW7?M{d5=lnjk#oYQ_(@U zQhxMJ-hLIA$UR_kTb@dLnR_T}S9~a5$%lQ~yO6w;;uqQMPl`qfJ%8Y$--Sx|r_~g3hmuuZ8*XMq@QlDI3`sEs5 zVlDgj;92qw3ft{o5WS!qBXCB0K@jwv!vlAMU~u`80O0?D?QeG&0N!Uzf202aK44sb zyWbuF!268te;fb+K3Hpb0|0Pyt=RwA9svGBLnh81@ZC}LKfp@D)#COJYX|mR9mu4P z++oxHBlJIj1J~Z{9oC=VaT4o~k*&oNE;Tc6$R6&^{tyK0F5$rbdVv|g%G>~6l6c(_ zjgz$PL%S$-hL^ALvB})UE#nhPUFnmQnK*JD4LpS14#IB$?i<(*^6hh2mN;1X2y@k` z7hFpNZh(YYV`;RsLB?kbH(J)F<8|tmUT9h1dS!O~7t-=I>fUrr@94Um-Q(Fi5qBf| zZv-3mT2orr8?1Gch}x<320gaK+h-q>B^>I~oej+G?J=|Ulk-Oh+z6KTc-ywN7Sv16 zc|*!{UF6Z;&Uk9wdoJGT5H8=~&KF`k;k5Kl=m?vLt4X_|)-Zc^vk&zO16-MVZ6)@z zK__D3DSNi{Y=4$>rZ9AzAHwc~b3ECfROlW`+vtK+)V`5`{jw0%(ib@%yblqPAMEoJk`yV-n^ zoP0eRK6=W*sYRQw(xI8qXDt%n>U2CY^f_;S$J5xoHmlL7(8gxP8NIcG?5iSikmboK zrxd-2Gi{7+)8LF#OJ?!NJd4vAl}GZ^(!M0-%zhr}X`+vGT$p_`JJ&nYd}b#5qc|({ zPLZGUcDeh4PxCCx$74O5H(qESQf?k|=#4ggx)S>$&xc3xuW7OWo9)x`%wNS_F9F+j zTwNzGLt~fms?hUDo`dVGk$7nEMdF)nI$VjJ>G&6291YXL#5~LX!=2am>M4w#8pr9d zu|!C$ufk$QYn;&KA6C#ocS9203rkdn#QHKUmU_>F4l0peNTN@|677e?dK4CG;5lf= zvJh4}TOo-)4oh^KqH?{DiT&T4)DQ=sIh#75{>n(q!N+sQdEdwS!WEhuVLV z#_cturRv1+ZYd9pJJg?n zD5`dI29BRYHHy0p8W#XhGr;?)zx6J-N_VM4xjxk29tUMhRIL(qQ`@#`v!+6;rKQ|o zTfkhKS5a$^LGcPB-OW_309AP9xIr%#5LbQA$Gg^ttMs_Jxq$s2gaF()E}*9J=SOUN z;kqwgTe*>FczhPsMqPxZcMbJ9c;A$Z;Hq+T?9Ve7u0^mvA1&*iP~Mrq`vw3200000 z000000000000000000000000000000000000000000000000000002iA!%@s<;m&c V)TAcW#p3_6_y>|`8>>yC000`Q5GDWs diff --git a/src/main/resources/schematics/trees/birch/BirchTree_01.schem b/src/main/resources/schematics/trees/birch/BirchTree_01.schem deleted file mode 100644 index 54f6914fc277db40b10515273c5d897a1be5e172..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 611 zcmb2|=3oGW|8H;J_rDwH z_08`;!++NQoWlQk{%gfQdwa-X;utQDzSjVo{4^(YBTi2e^bdpLe|B>%4Si$0tTo*rYf za_)=^-=0oX|N8vamtSGm?!MzLpOv&_g;rNlZuhDf*ZbWIl5S6`Quk|a)LvP*Bq?|8 zx75@ZUff2aH)B(cX4Xu8e0t-$lTH72d#E?sUDeuNJ+Z5J@5CiD-_C#k{N;K)tgqXxyn9wew*6%Q*+bu(;wFwa_+p^d3W>87qiy5YMr0H z-T2iUmUGd|?5)4Y6bFceJXv>c`tM(zJA%sJo-g+Q^!MzXD@zSuDyx19eb;^}R-EC% zxeNaf*giXJ_bNPpB_G4~bF=UMU;5VGTIK-LXD!w5>|g9RY}ga!$$b5^Pyr)@SeWPU zdp~|LJdh8&bYGR9!RA_hLgCc4|5f+@uVmoR*)ZwrZSCLx=YL}t3(NdnRL}ose?~nd jSrAJeIIDBg3m9Mr9g})Zt@7vg|7Tcqj9qi-dIkmnzOpW; diff --git a/src/main/resources/schematics/trees/birch/BirchTree_02.schem b/src/main/resources/schematics/trees/birch/BirchTree_02.schem deleted file mode 100644 index e8602a2e627940adcdbbfcfc7832fd3775a69404..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 686 zcmb2|=3oGW|8K9|^%HiKIQDV--M8wF7N1Tqpa03x!KV6>v+)SCsF1RZK+?9`H_I;N zUVr1~+P{}I|Im@sAEfq33C~%o#F19c+Op`sykcxx`Mk=RpZqerOqPDU^nPVy`j+<| zx$Z|h``VNqSr~17DEa>Ri8pn@ug*{XXjOJa^!mLJrKSDycD1}Yr}9MF=br5|`#O1w z_3Ccz?$h4fxz^cinoluFK?Y4(O z(@w2>H)Gx%eebU;FWsFt(X#sI8NJDU%R*Tl7x_d@UHc;1O0zcCtJJpV&pzFi+upXG zl6ZRJ%YkXnj>jcjvynZ0F~8`2%9^#w`@jEE=(4K5XFYezz4y1(z54c8{q|VUw(nMO zTvAk{xLQ`7#L;EfjXrvuRlj&5uCJZ{jkREV;P%y4$CY1I_|1wfOL?4AwqnnAt(&i3 z9H}UqdhOMp*(I}u7PqDCIkfZ2-;eRGc~^=yYHWA7(wr11>R!f`ckjn9|FZQ_f96`s z{`;MEKIOXJw7HQLy9#E->TlSuee~P*K!sO{8#QjLySulme~MIde7TI9p&y4X+=jZO^#(yngAN$>$jveAc)vomja)==8su=|$gae#y^>@%=q@<*dsG zznB+PKmD>kcuUg|L@s_jK!gs{uus~o!znV J@{A1(3;<2yRV)Ai diff --git a/src/main/resources/schematics/trees/birch/BirchTree_03.schem b/src/main/resources/schematics/trees/birch/BirchTree_03.schem deleted file mode 100644 index 867ba03382dab5ce6a4ebe7c770decaaeb3c5157..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 642 zcmb2|=3oGW|8H;J^%Hg!Iq>oF-5@W{LsPHc`z+XDsKIH@BBUSU(KErLW9GYa+a~9g zpM57He<1vWW>5daY{@yfKLS)_Rf1OhsrTG``{lgN%O}sdo4H-}(xjQ&1H->hH4W6b z{Ck&%@4QYAiBoU3ZF*Ic@^FRf+3UAf-RQlx^qt)mrLFq&_wS1@h~)dE)LWjEJX<_c zZtZ%T*vRPEyxl9FUfsIvOgQ_DHJhhv&3@t7F`O`PpwScz5I8ooyGnCPoC}c z`0VF?JYD&h&WjUI*R^h{wX9rqb)z1Ca&XjBzs)=Mec3px<m=x*@!^R{Qcn-^c2@G?yf=(*K4&KJ)jfa1|V6OSdpcWSJ`K$R~ejly!-*Q(EI7WO|{W1M2gM{|^ zUw6y@f8S<*o&CYPAHVWX3Ez{LnegS+!zfmU9mdtb#tRk{`t2uyRIp%wm3IcbJHftT75RreqGLKlT2=H(lotx z$VGB-jpY4pD_tWcW*-em-}K+-o!r*a60xuM-z>{4SFTzA`CGg1?ZwYdfBLA`$#s8a z&EnO?!hA|s_t}5lR-v^skomIN&AwT;w!U|qwq)VJ=)6`X$4 zc^m3a6wmp-cM}s|*ZV(Shkvi*l$p=ZaPHj3eQdpHmFDu{k^8cPEU;DIyfdK$3&1c;J diff --git a/src/main/resources/schematics/trees/birch/BirchTree_05.schem b/src/main/resources/schematics/trees/birch/BirchTree_05.schem deleted file mode 100644 index f5e8ec8e2304b2609afeb46ae5171868a0e0bac7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 567 zcmV-70?7RziwFP!00000|CN+qPt-sZ#ZNn(?zRMxcb|Q6B|$(El6~1Mi4Q6yMlm78 z%(l1fgl?Y_xxCc80so}ZskYGoegU`va2O((yXtV=*M;{{jR{)=`}q1K zNpz%DzA(kJW{OwQDA^yy2LRwgp6Qq~NEiM)(rL!ncHzILBIOLenI=hUVl&NjI{jlz z)9Lg!gg?m3=&}ycIaN6^lfdR!7Zg=0;UlFh(<<=des83!DEoF@mhri|(RKgF1pr52 zxZ0AF`cCzEtZP<9ME%{~m3f7lso*|W6Y5#~us{aBeXx$KIUTVznCeA!WJc>8fk4Ml~nOU3qrHsBm0B zLaw$*7ZR>xRM+;&C9f~xbsSfkKCvDvn^?~>jbWjhM;qIfq5&_on58uTyPaDa)MA;i z25;YOS#10Q-b@IV(R05ru@K?9*;Hr*<9tM(gCDFVA;6|RDUS=*tf`&{{srHid~|dM F006ma9UcGx diff --git a/src/main/resources/schematics/trees/birch/BirchTree_06.schem b/src/main/resources/schematics/trees/birch/BirchTree_06.schem deleted file mode 100644 index 54d771463993d46bd18abf00d8c812e0844aa687..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 822 zcmV-61Ihd!iwFP!00000|Lxk#Zqq;%fZ>zGaniH_R;*aCr$v-o0jZk`Bo^Ex1T3lw zInFp4)wPj5rLg7|cneta2s{;=GETS_m@#P!h5nyZB^}QkpGV7wPcUP@C@8 zC?{6VUpl{hVQFATEnQgd%E_vO&PET?$Rv5OC8tz2(wQ1>4{i*$H#fDJm+*d}ZCjOs z{R?+@ci&gH_7tB-mAxOwbtSkLZ7DgqHu-nyev;@|N9i}&oc=zWbMfGMGQ4ul2A8I^ zpXi|(%h{%cx0N1^jhwT0@7U8+tyNY@QYuqrWi$Fv4XSQ?60W6rylGSFJcv?djA}`L z%cd3=QDSaHx{%2uIrFt`po@6?bSKY;>(L8cuD;x`JsK^!xwlKIqUWkyAL`OXS*%ts zFFvatN@-MPR!y<37I*A(P^9^2q=u_Wl$NRr)ojV5y`d6i1FWm4G}C^ISzeg&G)S3m z+q=`|9|A7!sLx|IrmqTbnwRRO8Po{1TrX6+MyQMRLWSXYx8V(p;&R zX1D%xTszO|Y*p;J*<$}Z**#OxYsuq-&ihO7`5Nc#2 zjlZ1$fWLJ!L3tpV5tB&Sl;S%*MA_5wkJTikD~zg!&Bcu z=qcZ}yS~G=dwi$Sc2>CW^denf2LQj&^=+`BuRa@d?&I$9p5;TN6}nd_-X8$7^Vhd$ zfAe|Mu25(dFB|+1`QUi~0uu000000000000000 z0000000000000000000000000006*|G$;MFG>fZ>rTi4qk_Ipx$t&y5_!b(6FKbTJa3hqejO zHaQqZK~YO-VM+|hb==$v^cDITd5k_)FYeN+ozw|-iCDGZ`u_k!GIwVHe6^)gm@J2_$@>ic;rG5eWk6J&8?xe&qH+B%SY-iKq5xQ<`>Ai8TKB{p}aO*(@*f zBAY&)YSVd_W$@_U?!BG++Y=kLeSfDT!)k(F&YownN%LYMx7Bi{muh}Ac|18fJk+Kq z?PrCy$Ep(SPy6KL4UFra@msBOFcDn`Fu;--zq(u8@X$rerHcp9j&TL(^8qLDqGQCRZ#U9NqaBLlS5ll z_&m;(F)EP$!j_g4acUmNx{%Yqn$^6~GB3=0Y>Fc_D)S@z zbanWHgrj3+W2$)vtM|aqj?|~=R+(49HBz6K-3;W(#oU>SU#oI|s!J2wvy68}ud3Is zG-~}gEq9skuR!fO&z7KFqs%Ysl^K3onIBJhVKqDTbJu+noagS=Sc2VpOAysa z^;Nx8y+B^9`$a!beyc{E59-y~uaW9ey;Or5sUFr#HI(r53LcBRmFuCg2rdud^5c zS1D|@oI8Be)Bj;BTx&f5*2>wyozS_%+rhBwOiud)o8$d>6Qt)E2jJrAu_(Od?C+i@zTfk;urv9m#{%#gc=up;6MFl*f0-?s z?H$;pey{JF;2a(d*H{dItL16`!O$~7xBvkETZ2na0B~K=-T(jq000000000000000 x00000000000000000000006iay3*gv^5pR8(rBYqdG>#{{sxz;``aI~0021d$NB&O diff --git a/src/main/resources/schematics/trees/birch/BirchTree_08.schem b/src/main/resources/schematics/trees/birch/BirchTree_08.schem deleted file mode 100644 index a148c24e75dbbf3f54ea4de95a796cc64009f714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmV-K1H1emiwFP!00000|Lxq%Zqq;%fZ>xScATVKR;*aCr$v-xk28+a73oUnNj%mQ zWlh|a_A{LqCYy>#vnjnh%BpWIXScc%JJJ-J!A3 z_b8`tTw1@hc6oK+My+06i{wnXgPD#Vr^+T-K9RF}Ix}+ov+mrB7Zd_ZL zg8L13cXvORkM0l$ql)gY<05H2R1+;{HunBpeUv07HY)ugn{z*Bb1@#=N`}{Ny5O=T z!E-aTW4W*-;XQ3eV=EWk=6!dVvbD@ANeXSttXxK4%0cC0SHg`ni?>}$?Z+zB*6OC5 znz+>BTqX9lGP&$Mlk?x(1}2ZkFJ5Qaa8tc8MgQ%V+oRExr-ye*^fcC+S{1gRsI<^4 z(=4~+zRh>^N|Eij&Hd)r?v=>XY&6otgBE=-ri{JRl=Z{0i&PCZ?c0~SKgg`jCcg&R z+W#l0o7MjIep-7O`m$;@<<8;QsaCIau{kt_RqiPLwUw9Ui&t1ZwZDm4h6-wgx>7Gx z_-D_!@`WgmR>p4ENOQejno*5VH|mAz)ChI8UZ`%3P}k~(>eUE!y|+i1=-9KalFdHN}E;QvMa1OKl^aMBq7I4+*@4!rdr9$6{? zj+m!B!=QSHpwag1K>Y*rD)NH{}nU7~lF@FkAgc zT;Bup%fEc*yTRv+zyuG>DgXU@-~7-0J78hW`uV-UbAO=x-Ua}3smA>8S>I=Q4_H_; ze!e#fqssZcTip#{o_Q|sz4cA`0}HF`eRtoh>|X9IA6+s4{x0AAU0wdtaX$e5QNHP& z@(2E1)HeVC000000000000000000000000000000000007E?z~ZKPSeeRpb&HM%(X OCho5fobj3qu>b(Gc%}FN diff --git a/src/main/resources/schematics/trees/birch/BirchTree_09.schem b/src/main/resources/schematics/trees/birch/BirchTree_09.schem deleted file mode 100644 index 91177c1bfd6ffaf85785501c077d48017437504e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 951 zcmV;o14#TIiwFP!00000|LxpCZrer_fZ=CR6e-DavdXH9?jr|w-85|gZHxryqHO{c zZ5D=6P|`>uOo;|LuA6nP&|~Bny-Ux~l{=*DCUwjirhubb`ai>vOwPP{ruAmVgzSz; zN7_G*W@;Ykc;rd&v&!-~U5JS1N%vW#qkoRvKki8Qz0%Rx9BoR|oKzxPzrT6=XV*k| zm1fEK`8d|$yC^$P9vwW|-`gFUUb}nyq1-DP#Ea?6B+`>Ko6CK*n8ph=I~zV7o}HY; zx+|?0S!|9KDVWdT;^N|u;?WFo)hq9O*e{gk57Au7y~Fa$-4~NdJc^^_y=)%*JDZ22 z;Wv}<xM-TAZh#u4k5{ z)2SK{CQ*{BW7BcVlh3aAJWtc{QS@7!4_+5d8lL=gy~#u^M(RjKxn73qFValU20A-a z{X9K0PnU;37_)z_uKFA=Uj(+;WxQqRW$%xP-Sp(y_1KAuepUI=IL>usMj0IRPm3R3 zuGQjlv>UIIU;h%kpXbd1ucg_lk>-BAG=mzUzNi-}^klF46{XYDo4d7ZWbmY32A%(Q z&8@GEVzeT5w?>*@)=P7{MySX2LiK8d`l?>29SO<_G!l6$YhtTmJ>9UMUe5La){?XS z0jw|TAJ|;fKd`x|e_(yN>YMrp))(~+Y%b~_*jP&jHO?`#3!PHNdt2d271 zdy_`nHAd%S5(4na1dh`Udy}we(zWjp976zDkB()d7mG0TTf5f8<{NP3^WU^%-2YsjywPu}u(CV}k(T7Wj_R z3(L~2Zc_9&TZroN_KUtvNBwhy09c7y>wonG;D_yUgJ0di_sbiEj!{_ufmN_qUY-3x zxLo8b`g@_hzB}$D0NfJSojLdZ!2FW$Z(I9U&kxJD7HvD;U<6<#TwC+9{(I)*W|#Yi z>iMCZf2w)|SS{!Nplq;Hb>0t)wY|FhEn5cwD`$W2u4QXqTGqE^fv5a|yUVeD_gwYO z-XFNb>;=3-eFK}T^3DK&wa1_S0ssI20000000000000000000000000002I*w)75@ Zbae83p<^AZ{OVuf{0*;9U;Cc1000*C>oNcU diff --git a/src/main/resources/schematics/trees/birch/BirchTree_10.schem b/src/main/resources/schematics/trees/birch/BirchTree_10.schem deleted file mode 100644 index 14bcfdcc03e45b12d8e6b9adb95fb1c21215c2a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 939 zcmV;c162GUiwFP!00000|Lxn$Zrer_fZ-!kCUr@gth(x=``Ce9H%Oa88zTX_Xqy1N zWMLQuC5oZA7DtH;T(=Y9S&zm^+bBo z{dP1n^VDXeNRl5+S!KmSM50Lg4^x}|RdV~VC!Md2O~UMG7s_MPnt|RSl(z%ga`^$f~A5SLPC`xKGtA&>$pDTl{Jf(AMc%^;s&8UX%;GJ5bA!jP@M*$_L_z2`Vai;->Jq& z{`^bTYtYS4n{~6_Ak@8Pp>9aBJTavrzsmK{Y6Wi4+W61<0^o8*YW}w`r5^yUAnp7B z;QCVXfAeyB0bm`p>Hp>Q0>CAjjd18L)v@Ce=Yq1sQja(KE8)6!o`XI^r0*taD@+q z@n1!+b`d^_z1J6sc88w@ejtpJL-D4601uhpulx=l=Lf(mbOIlT++`2jcQ;X<5Mp`& z_=V=bmcs%2P6EgO4&y(dSnUG7vTvo@zwabg+AE8T00M9Dg^aNc#(!Tu4*&z9lu2@T zl{uuXJt{X3)k~9q+#vvOSl=R=mcQEgAF?z7V7*rSY$1RvOq+Ue`yaTz)aU!PANZ<2 z5CZVtBENzEFVY_X000000000000000000000000000021(UqI~c`-VAw6K}YOx1f? N{wKcG?=|+Y003KT->Coq diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_01.schem b/src/main/resources/schematics/trees/cherry/CherryTree_01.schem deleted file mode 100644 index 32657a39759c32e48caa4982f40567a088e0d2c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmb2|=3oGW|8K8f%(@&PbNpld+5ohy>1C*p1gHBVS)RpSqG+H zHJ7Pxy4==uO;AHD<=@JzZNsnLyqkS0&G4Yg-^&pHsxvCjx{nI;pSN?Ji`(E|HA0IYm>&;s; zXZ8iYC;QZU({FuY58HP4U2*xWq%9tzt~5Ixwewa*M6a&B zDX}xdahX!}0w1-_xt$;h3mTG?iuaTNmo zwkFBi)7W}O`N_*0Z_nk|FUj%$QtEqg&pw|Ef1ft33t^cXdan4~;y*qY+SZ&~SE2gJ z@gLjP+k6ZUu5I|&SU>H#+|u^!x|fCfv;JQ9etq|DkgIJ&eE7zFHS0^7;`jaOu1|d` zyvNOzef=XAhJSPJ|E`Vr=#?*@()l&^DkT2NA^-Q9-UJ0x#gvHT-gD9M$M-WR@7wdk Ie?0>O01SX7F#rGn diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_02.schem b/src/main/resources/schematics/trees/cherry/CherryTree_02.schem deleted file mode 100644 index f545721ae50ec527bd0d890f322484e2f2e3a821..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737 zcmb2|=3oGW|8KAP`d@L7IPkIk?xnY7kq@7oJz}lTC>SWD$0Ee9rQ&8GkaX%@$@Yc2 zP2VaMebfG7X!D!%Pe@$BF0Kp?W7Z^t5C2tXzdd)__`Kh5AE~aEOEtG&u6A59*)LY= zwwC^+91W$fyS7|YmYuuR=WVOdo-nIdv%>ssF0XVswy);jwtMD*Wt(i?=9LJ`&T+5Y zV6)v~yMg`g{Is*@^jVA|`8?Y8t~I$-IE_EWi?{k@z*qk7;otcA`GoEY)f@Z!M9*yc z`9ob@{r>6v%|~9YQQBjnFYw;iB*18=*)Pl8lE*#OJ1^O9s{B!Ic6sh!@xt<#J2~}C zCGUIg+NIy0{b+xP=lb=jw_^4gou0BbHLNg3BQ`D7=vSbh)_&)Y*DtLpP6*1%ab0;S z>grNkgO#P9G_Gj}PCET!e$M>uGvA*3{P_Dusj@5Uy14eP`s{3Ke5UAbeDC5d0miOn z6=k(4-z@z%$N87~M^Bk)yz7qf#^bL(y>R-lSm*USxA~KjzV46-E>`)V{q87_Tjx$8 z1OHRUS|6(KqT_KfR(%=MkRk%0jK D&=Oc) diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_03.schem b/src/main/resources/schematics/trees/cherry/CherryTree_03.schem deleted file mode 100644 index b776e1dfb6a49572327832cbbf9af75210bce357..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 876 zcmb2|=3oGW|8H-c&AaU;aNy&_Eq^vFF|qs_w&42_$2%sz!Ux0`vsGox5^pPbS1yL8~$q`XG9t^e(Bi0z#-SJrdR(l;AVM3~jh z(R~~Etm~%7q~+W4y0TQ27EkoqcQWQ%-jiehA}+aKOD&c)P5QD#RPxWi+w$w{F6f+J zwQJ7%bKKMSo&R+5@9pyZ{JS@8E=0~Q-xjp<9dp;!N1iix?pf9Uq%BsrYig{Vyp4^8 zosG^Nmq*7-7bfpx`RfM+|GxC^I&wSm?2k8lLe6}@IdST1>(}w-^3N*`wKsjrzgqJv z(bPPho&VF%z3WYP@~BpYow*xiC-3!Vt*)52y7|`+*0IO>f35noXXCl>rJ{eIs{cH` z@rZS*sr8jflhh+K=WbTsS+->9*;hK{{F@g|`)d62&EFaO?v(!h^jtjD^#18O-C1v^ z|CTb6ICsQ@4i0#H(NXPyU(XfY3Fk{o9jzWe`y2^g};yPw;j8A^<`1|rC%}2{MW6m z+quYY=e{@7wckyCUAI5RbAC+xm(x@Go-D5~x;kh6$HU!ekD0=sSlxHkn^ibp{Nd_4 z{XPw@&z*Tfd#4gx?DO{jo{|i9P3!)#7i9LnsDB{DZT_R-AG@8@Wl2HS z^T+>GF;Jf%>!i`NW^DgZSobt-7 zXkFnF-FtEKKUzB!@Fz4MczW&X{XheU-Nq6NKCANHnb+&-duyNDEUCqR@9t{LujQ56 zuCwu8by9Lv8T;C}Nk{70W^M_+A7@!0xy9`G%a;WW@81jT-9P(4tjWjMvoHJ;IR5(Y z!#@IV{nwtGaZOfjQpKvbxnG|D{9O7dMN4=2-_6gN8P;w3@b1D*#mhH5qKo%(UYPJr zH_cY=MgHQo)>m~GZEY0ZyEWV8_N&*g)Gw9&XJ5BVrG~v{_qz$P(j6_|+4tAo`N!y( ze@}RS`xf>W;X8I)|6_(^4N}M-^KVYb2Bnodu{#fmZj+qfx1Vj}5vF|>8yOe?(Jah9nmgsw3CS!Bf7vBtOk*(*yzZ`yIqCCm+ z`S@_0meK1dAKkxw_x9eM-4nBF_s(7o%1M@=$xXeOrVBkgKY4I+etMdg z15pq2)SR;^n4j?C;^LJ(noF!#74hb}Ncjq+qtK1kABs%P@}^*M99^ZeP*6TKMeBb^lGewm-^okfpV>KHO9IQdSIod> zeY||99Bu@k>*QBm9F5bWOw48W@9z9!UxuR8i`Dbph-zc`U1Htq7Axoy>wdRbs!OcT zyTuB-#ML90Jivc0Jxr@ zc}TfeD)$s6VRej}W_eNd+v|n3!~SLMhYby)U;U09DYkyS;ND&UA2vsIFZs>oqm-$w z`r2HM%Rtn2)G$NIw!Qk$lwkdg$Xg$y+6ECu5-pGQ~z3q@6HkG#-GUXrJ0%?e0>N~C~tiA?pXxn^(RS|AlUpew>XQ<0V zV~)#lX9X$=2F(*p4+z6A7KY3%I7Hy|Xc>imcGZ}@hNH$zfaYQI1YG@L`4Ps|Kg|7c zHy`J)y#>zp0bEP#?$cFT$`*0s1oQF0)Q@9>v6>Jx{RdR58uwl0Th+H~xauLU;emUt zQjU9MukE^pL2G@t{eP$Y@vm~$PWuOl)-M>^yx*UXTY8J_pYG<>wjKZg0000000000 z000000000000000000000000000000000000000000000003{MFT;a*Hab0Alxdmj QB3_;U6Jx5nOO>$z03A{J=Kufz diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_05.schem b/src/main/resources/schematics/trees/cherry/CherryTree_05.schem deleted file mode 100644 index d33f211d5d83db63742b7fa8cdbd17d4660bc3b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1200 zcmb`H{X5eM0KmV8PP>btMJv~Id0XAKiY<)Qjxt@6YFv)iqj1d1FM+g9v=r`X7`#Cj8_qUxp>$!%AC__ZEze0+QB0eJ33MboAQG6N<4Ey5Ape2&f~24(`STcsXw!m;vki(BAXb z@EcDX6LAxw%LcfhyVCxc%n)tgnX|B|xVXLDvuYZf*R_I>j%Ntsd;Rgyr1LE()77YD z>g|!CyY7|vBrF)1l*!P_)Cr9*XU$E@*{)0spJUvS5mGRGle>RKV;-+Q1_OE zZk6DthJmITd&S3+QmAtOs^Evp*0HbhT4W3fDo|oK%ApHcXdyw)22+w&@EFeQhqhi1 z&GsI7ODm=*z7BL!*NdG<{Mz>87UOqoBD>)tOfyo+SO{BC>7NAwfF5ng92-Ua6BL_E z0M61{4^Yts2Z1}ee)<_GpyfiGJxR{#ei_~e`1&`qg_0B#AlOjDI$*vIKKvqQb^R_47z?1X8*x++7&1?E; zpWNVF>UIt!yJJ_lS!DQ)-#wM$!#jE|k_s(+U-cNoT+jaYdfQ~Rr|@L)&&^xhwjFZh z|2c*bAr9fQk|$I8(OfdUzdi=*5`@TaeBhav?Qr^onH+9zwWryRXjYQF#@si!A*kK6 zW8B2{iv7i$h$(nt&Q3d`#nno*z}aWBw}Hh}k5;pbMOG$cbmd)HvNNh&vHQj3d7@xr z+0dPb;W9-54|%F)$1JZ7LOSAFrW$`|lR{}iQH=(}BWSs2E=cUnkkagQwPwJ+KyW)) z$$NR-H$BzIKnKT#qk{L7Om&*?2M=9X&0n`Mz1JA~*KYS1ICXc{8hxf!q7X#~qr7DB zMN4w8c8&;R*N|;tap@JucYfT|ti!aMH^D>%;;F|CF*$xE@5SKPoz``A(e0t^a;eQ= zb^s&p2BdMk0p5K^y2q#O)Uu*<`zX2S6 BJVpQj diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_06.schem b/src/main/resources/schematics/trees/cherry/CherryTree_06.schem deleted file mode 100644 index 9c97a15c4d7a76e5dc66b74a2f68b8aac4f613f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1131 zcmbW%`8OK|0KoBJn9f-pJt(TFx?=K*MNn)D=15&d@Mao-PWx+BgKbwnCXviG+CVBa4;f5O*@_&MO;}wpI?~M;a&(B$&Pc~O7FjuhS*G_z+$DWaeU;z(K`iS(g=#TL7@mkg{nh61RuL*n%X@<@aM<~QR4K_Z>TDw!KDE9(^KzRNPxg)eGK_z4cI3;I(9n9!L$-1QX zoj`dpwm3^lw=fo+j5*?9jyBPnehRrZgHK#)NlFDh0fM#ANoV}2U9zbM@WqEte?;qQ zp^W_9C&}yVYIeMx(+YVN#R-aU%wXUL>os9lh(zU#j@WuXh<>5;vXAL+5*q*WEb zgvG@88fI*8BS~LK*)Ck#k@zH153x;kJY+SD*oSV9K>yt`lW{a&w!9H+=Xf1&L0Jd^ zn~5oER-!hP@tonvOY%q`*KAw+^kDKhlaU1!2Yq?7VLd9yp0%9#z$l4Lrlr*Ie4u{M z2F`R3WXxONo&rwSEktu7qj3|~w#tIyFtq*y~E@w@I^LsG*yAWU2Z?oKb-p{HF=|0YG8oLE2@<8ZE_b^{%FSm z=mWUpk=!0=5OQy#Sgn@T4L-axum)XLPgpaZ7IWiuUOKbWt^eTn^*JG z2w0`k{HBXOTq%V@*irXPU6g5ipQOU~D|-Z*7}xBq;!_#>ydPLt9$_AcLM8zY!RCQmq#nJ6J9x;4s6vuU9HCc9{vh9B^CGs?B(YJ* z6sb2CkgxoJ9a7%DL!=w}Hjw3H4F>4;? zEtI#1EhjHejNB5Dyi_ubwwc*WcF%M7C-nW{^Cx@_AE*NUOUb7WV`mVDGjmT6liPb% z8eJsK8Fn$Hwv^KdHT<|OFz5LGKJ)092(2Op<>_kFtlx6@Ca?=CyBgGmqd~_H+{ec= z+y!YABhu&ywQpSU^+0WwdDXZ3z4q)8Ci#ttQrr498E#aC%3W+~Jz3Vi_W0ak@?agt zJV5?d7jgBuV5TX}ap!t6@r>0#A3YScXTNpen1K_qLm9o{{c!rubHg`nYk9~~)Ss=f zr>vkc_mQJQurnK;l1fUv{g*yIHxcbE z^gx&z%K{JPS7q3Imwg;SmJD+JSi81#&<7;Wpz1fG@d4H)-K3NaD4+b z`}3FH!1sf3)L{7pu*O}i5aYK15p1m$pRyV9Z9;~o&aCbTrR+-4+N3< z1W_SgASRv;=CMC;%JChE`~M&mz8;YzNfe23*&BIHvF?aX2OuZY%p-r-n22;0^aN)I zn0Kqb!itw=DuCO`opn2Mi4GVrpTcD-C7OExA!?YmsX)UyS{lG9U>%Z>l$(Cz+vKVN zMLn|;7j72rK-;;+bEq8h+6k|E^;_FI_wvf~eW2RmkDg1OgCnDMwOi{_8Xh|M^v^%Rn$k?9g;ANn75$aOFhYFUNnOh+{^4~{FV)Cf&lCtCqmt+vZe9?42nk8t3Z~4 z>o_oOJ=EpoQsjd*bfbDf)SxhjH!Gf#ZdRh>`9K#Fr%q(eC0Sc(AvqF6b9%svljc9t zrRu8$xiRc%rYAUFV3#1`wb;6Yu0pXC;^lD)mRC`odgdIp&j)8Ibh|8ef4PWyn-H{p z{-^3YdWk95|1LB=3sq}@7#g>1-vn!j_3I0YK;A~k$z7Djk4!g=mG2_HNp!(dl+{W7 zqTwI3?AQXkaL9uQ!Z{=Eg8O7sy%99qSDLcanxk5Asd+16aSwkAo~5YN3NAPF9a#|v z>0ig#J6o;>E`e|)z)tuFQ}Y*wYr1s*mJbANo1%%xo(1zd$nj?IoC$#_`cf>!09 zY2)R(mV2orcKyQdUGp^jbw-e}3#=LUW}8vuy-D@R;>m9pX^7SS0XO*7Lwr| XcBS6LLJ^EPqXha*KMSaB0Ra95s`84M diff --git a/src/main/resources/schematics/trees/cherry/CherryTree_08.schem b/src/main/resources/schematics/trees/cherry/CherryTree_08.schem deleted file mode 100644 index 4f8ccef6ba3ba4c7437dca8307a5db7ef1e8ed42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1321 zcmbu<{Xf$Q0KoBY4<0)t$wirTnnRW-4~N>RCe?EF@(?woEElE+ow2nfPtAFX(8I=P zTbtd=WAhL@+9I2Xo0w6PW7uB9))w3D^}72L?(@U@PxyfFhQPn+7N1VSg~zsX3O~M4 z7P!3&kvb{-H)oOK#{z{__6ky>eaI=-&9r{0OSns0apI#HCo620|6x#-*?WW807U^| zyX?=Br-u-B)m>lTf%1`jPp%S!pxCD=ov(sDxB15m;LDNwpQI*_#x)!8)YNNLW1uYrv2r>FE)jqu@ai8 zdUoo|KDxg6bH!iNvv#1~?gyH`SHUnCx-3{ldaHgCl%eaPP$)Vo)3?WiwsrZMTNAFo zz#*kY(P9J9&ulM8c2_3Yp6{QDLFpbtb#nq2_=(0fA6Zz=ih4`dWbfM43sMe7QpdR16^g%2n0GI#AqdOPol7*SkNHsm6O(*|wEGq;M8Rp*3xCZ+;!(F9 zpdD|uEBVvkkDQK&pc|V+7l*zjGYMekqnQgJfh5h2n3}#$p5-2uG{}FV3XbdSyWT=9 z58-qtU|HH@r-=0>HGL%wL&lG<_+2Gi9vDm;JL#N6QiQlO;1&!puknZlgBKUk>wt{j zJzwW>d<7quMz|Kum!q-*%^}zBta5?YhVIhIzSlHu7#}wrucWQ06^*%wxU~7TwdSRv3qUQ zo?r&FYXvx1xruIc)MB-C%T&RZsAnv(z-kS@$|6T!1O?{kMR4GUADTWOnb$e)Tn6T{ zid{*&Llw%d;@7KO{Hf#%k$#QX)N5b9Szl=KuWA({E)Fc@1r+{NCcxbn!R0IcB~^}CEOwyzj; zc><^vTGBdJuIO}bOmhk0DeZY*iH{dQ-5;HcMxpO$bOZOKmcDfi%MA$;=au=m~#VKnd0} zF@GskfBKz<$D{W>u?15os zM&u~^tq`4Z1wCACckG=4zph%b;WhE;UTx&LdoVA5lf_7$H|i6W?8k$;|!slqoZ#eTA4AkzVpPV$rzN>FJi10yk+*Ypu@0^M6Wd0tUFj|{?)v!3 zq-V65p{tfGmlc;=l3mfyWBWL<%+9Gln%IPlhcs#rHta7%lU066@yew__X5g?(&0I_ zfO4*%fWN5Su_+mjZ;0CRIjMr>pDFG&CjUU)gcRkRnw3D_*d8sJy$}sE>09W4E|{6? z>k^40H$t-?hAz03VCNkzT0aJvp-L*@If4bc1^yZfQ}i1iRh`95S{{M@$~EhErxhhW zhoowKQjSKgN{8|zLmO*IwX0lrXhHqrLr#>R=oOmzm^ICYf5xW^_HA}ulDS%qfxgfz zJb40}R)ULKjMtT{$h**B6Mo>LGZcA27x^TX*7=5u9q-MZ4_v$eMXs1~93B0OEb9h4 z`vn+$7b*-l*y|PP_&GKOOs=ZUvg_u8_%#}FHMKCk?;*+xd1-fU_}sX>CYuWf0J`~4 zxNo!i63pO}>Sm%6&~nB`h7x~?oe2lvzAdsf9Sv8BBCv!};Y{SU%0y;^`+&D^-kXGn z-%VEl?(E8bZ8$&qExu0eGg`fFp0fv#Dlg})Z%Kq^8a4_d`S(2&n~p}~>Na^h;x0e3 zoYCY%e5NUarpw!UP;sdHhb9$isWr$L${g00u#9w}`Ncs%c@h%p(~;ow7bP znoA#$#lR=UxYp9P?1|RUgb2RdQi5XN&P=9o$IS0PL+H2TW=(IV-k;8*QUk)z&&C|o z<3Qo>D>>w2&!?_$rwR$_k8Cfs|ZZoB7R7F(~noRGOXZ(V?_%+=k_)o2~ zAw+7`#h|dwOv-+ka)oHJTy>0XP*f)AHB~(f-B#V4St`#-JiR@7{;AX-ekjY~{*NxWh0e2z z2r#;0$)S73`p5dY64LtWrDs}YU+-3ljnYj|11c7bjWx^xiJOvfGkdc~zxnCMvST0z z*Lz_hIPsQBt?+(pP~sSY3J8x=+FybD^(&n!&z(_O6rL*2HZ4~@PErOBc=B`|wn>_w zpwWJrH`wGsTaVYcs*tlfs;3j`Cfd4r74M(z?kVtdr+~GCc3Q(w=1D(CsK`uy{N{#hfHIQDND(-4DznS-XHSvgi%g#&wU2||H z$O-R3K;+q?8&5pd))XeM=|7SBpU=`*J zs5;$ITh26memagbf)HkC8KG8+o4fXbi~*R8mfR!{A^=-$_ZaSGjk}6Wg>pU^DCZ z^n4k3GSyztw8lD?+TC_c%nCW2;P*-83mV6xNq%H~6aO);u|#XGH!eEMRyDe1VTsh{ zLw#Lmd3{Iqqa?DYeDaxuGb_+&jSU?Hxwm+O7q|u_~Hnrmoas-+0ov6 z0+q_tBVMB)ZnL4SN3`-LE6#0xCA6$BEqikD@bS)$^2!5j*%&BVNeYqp5cAVluJmeV zL5i-?ujw~7W~m4CXmpfRxxl)jw4uv zI> z{mxBKGW#%u>Uf_SUJ5c$QfINMTFsJ^NVszMH?{8#+*~4Yf>JsVjW3>ze^_}14bb9jY*SY9v*35E z(j9yCGj$tN%5U@r8(9aNqWsw?-(|Kxvq_z)46mMpGqx&8<%KEb&Hl{xi2PS+*E)uP zU9t}tC%vzd5}HC&x~i`;72*J&Z&zn^$sFCE@7BnPT!1zUD zF_gN2Nt|VC=~h)JkgnR~uy@>e!Q(l0lrE;Y&yuQQE7vNyguMnwx@WyHnHqhD@JKuC z8!&Zxc+;>Hg7l+d#^jCPILS(s%wdCVX+zGMI*wxb)1>M61$k*eM;?HpEW+O=tAx4o zPp;$-UXlN&L2=Zc*o~c1DK_9b`+v_HD9USGt}6KSXe-8N2+{)5&DKSqU$> zHP>Rko+zr)rW?75AlHAp6nbv$>oaWl069jwLClmF3i{A$#NP?sLMmv~h0$xg!RRLqK}ei7VjV2Pkxgw|@WAx}9PLE79cd%j=Sw-I|$1 zPFZCt3cQ6dh5Q0Fx$Dd?YTyQVGt33sR)MP@PhWIBHj{UGB@(>e85?B2EZh`n9wzq6 zG^ZtZ8%|WduqS}>nun`}17Az(hG_jRQix2XXn^Zr_M3J?C-j4!mr*#PTzy)H-6W%; zJv0m^`YyUl&SSS1+>6^@>mbGo5o7fi(FL(U#BBp!)1vN2Gn@f%_gc4+yovrbPs>zJ9;k`b;eSOeMitAhcVtINkk&{ns5AHqC;N z(N)5An5h)nC?g>;o{Wx1lkr&Sb#N|~Ak37e(Vop_-{@-uF0yj3p67!7fu{(YC&hH{ zG7N<;c=ANWi&Dj_Zl`m&*FAd24-bwEa|4=v5$G7)1~}&^qF94N?giB#)G?C6Fh^}9 zk(?hUiM|gWI44Qwk4aDKiYKTwGT2B-m#=uJkGW7#Y-6Xwd?nI6lf(G_Hs(0sUqs%! zzo96LLBAdrqV#cqJlDODCpqrQOzF6%)dY9*Y(j1cPw5g=l0^{)3j`NAhP}byWyLO0 z$Eb!=uJmwZc2M5)pVu$_Qb3_)RehVmcEvtLyrow#ydxwZ@CbWv-kPW;gHQi*2EUVF z)(p49;QGJsZ$aK#CA=W{E8UMrM=!W&GEQ$~2kK@$gFS23a(n||-ebR*-DJzetQgkZ zuVpYck9Xz)X3nfux$3ft1eRQ8u_pkl$JGn13|WJXq9xCyrXLMbXO{L8YKrOn;sgKy D4^#E1 diff --git a/src/main/resources/schematics/trees/crimson/CrimsonTree_02.schem b/src/main/resources/schematics/trees/crimson/CrimsonTree_02.schem deleted file mode 100644 index b1b100200ac39d82a3c6451669b602b39d38385b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmV<80UiDyiwFP!00000|E-hHYTG~%#z!kzb`(MaJqAMWO@d28Tmrs?26||kKxupk zu31YXX|eu<)wpgRBrnwm>LsD>?8>fH+%_$AL1^^Ne82sKRslc&8}Gvub4e<6p?!_I zQe^=Et_z#5CCMMk&M<(^1(J+d^}ypM0yy^%pT2%%%AnL)bRH?{tSEl(@aXVmw*%Xz zp^D@xlN6UahaD^uRbV5dN@hreE^OtTE7UTkQ>irA*Lc3tLap+uEJo?wZ7T6bepXfg zZpc%P3%jea0x3m3nn_KgvDw4qbm4j(6k`$M4a$o8q^G!Fl$z3hEVBytt8&VjpgZv6xMZ-Xz{KD6xtms~%> zPs@R)*Y9VAApU`oPkg<;1NUPTjXDGX07H}L(f|Me diff --git a/src/main/resources/schematics/trees/crimson/CrimsonTree_03.schem b/src/main/resources/schematics/trees/crimson/CrimsonTree_03.schem deleted file mode 100644 index bf1f5573950cb714bc565b6d17d7d51abc3e6489..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmV+n0`vVJiwFP!00000|D{yTZqq;z9{zWhi7GU&dSoEsNJbIzD>Z3t&$N zc$p4zrV=3w*vB&EC1$h9*xJmgT^sHmyXJp&ZYdRO5;6p>;YiTqjDfjPM3orOp$US9nkgsj|kd#B62HX`f^+ z(iGz*f>fmB3`g}oU;mX|CvG4&@E^F1smWaj-fl~7%)7alx;yv%Dq9Kn9C+BWSD6%| z$ju1>@Ev%!b?1M4tKcr~d+f%Ny9>VgYZ)E70c;r7CUE2U|4e%=$8y#q+B3e}(T3fy zd3Ax^Ch4i3Ei%Zm{W@alI?Dh-OShT~Yu9cv8{BGhepmf#b>r*Ng85bE!Iz1tx5d`eSyA5pQA6-LkjM!6j@UI*BF{EHk$c%=KH=~tyl-ZfvqpDk0DpW zHKB2ZG7)hE0Hz7s@44bLWUuRh{RtHx5XykjBmx-U?|Z{2-DK^e>a^@f6`MM%)1h2zRw2s znTH6wXK8wH;d#OpJSe$%p1FA4YBrA#S|@M$@zIH9u0g#c2FeGg2G$1@LtmxgN75kl znYQG42`X)g?EKbE^t^MxItyZVM1C5VJV2$;gq@IlxsrS8lnV*zGF}y!D@B;ZQO}>; z_#ChKSCOTVAUq`-dajEDUa%rXc%+B(~SQmSLoKBMAuwygZs0 z2#aYH0)VbV^AqMc_Z;KZOME|z72DN33+mr- zK;ZB?*`D0GE_EpO6%{Y%D&DtSt@D%i#YcR8b|IN-fVybjR}{Mgq$atB%Xh5Fs}W`84Eq;9rg+5M2CYw=yDi#`6Z?dk~ZEHK8MjDj>5k8 z_~2o3kAKqm>aizKnhxD6%q5{i?g@_hm5cqD9ETC(UTVkDMA)27$njKM0`sHMhz!%< zlw>)Qy{1Fwzq89&Z$2WMOHT;r+$q%>>2)1m7wjS79%20{X1qU;hc6IlU2{pz9m^Z* znp4QtgWHVV+(P=+mu~6ssvwU&7Da)dj5k1In}OK$(6WKBcb(;p*qdM`ZyhXd{vGVJ zG{w}Gd7o?5meqa!rRjya$FfSXy|xQ`z0;*N3SYe%M5}jbF{*%)KrMF|tIiaq3MxdX zWTDaqW&5(6IS_%VfrTL5XgLh2@U(6ta~XD}S_zHv3b3G{k!NTq@r$2)YR!`9l2H)J zq`XEEMyjD41zO}nDF`72Qw1*yDPSrisw!UDLS|M)ub7Gjbx>&~Ukx}&zEy5QF22A> PoL$X7Ogmgy`w0L5c?vS| diff --git a/src/main/resources/schematics/trees/crimson/CrimsonTree_06.schem b/src/main/resources/schematics/trees/crimson/CrimsonTree_06.schem deleted file mode 100644 index 10cb4bcceee4e57637021adab77954e40ef35eae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 619 zcmV-x0+jt9iwFP!00000|HW2KYa1~T9eJ&_<1{2tdg`V3Ho+w!E`eM^13k1&pb#HY zW90R$S9m{yRwhmVNB>XHJ@nu7(i~iA{jrk0w&Mn3nMJcRZ{EC_6-G6H8f<;>0*sjC zo&%LD6e&+)0B{`Ge$OPEdiGj1sD47py2@LIvI-&q`EmdK&u_)jOhni{?{ZnqS=`3K z;lbN_6?S!i$GyvtNk0)0>|xyFF$ROqNoUaSbGZ%fh2YAkORMMWa5(&>b}ev{mDc9- zs!;ySB81(u>~;Ub_qoSeXsCETQt`S`uOIC4UdV#Lh8Eep2zx%IP07e5EO~pf<>UfLc_HfQ z#E}AP8R~J17COm-z>@l=!_AXtY3O`lHQ3fe3&6yRW{;6T4ELfEIi6Y?rvT%isCp(n z9RrL!Q;%DsMwn_nm28{UWZRPp)b2bNOe$ug0iv@VS&eG(<1kf!zD655ff}t@^O~WX z*Jc4MyTGufM@;64w&_@{;EGeZ&a$Ut4!Kb0LyB*~PIj9)kEOb!p}N$J)<5qXe@h(- F007O`GUEUM diff --git a/src/main/resources/schematics/trees/crimson/CrimsonTree_07.schem b/src/main/resources/schematics/trees/crimson/CrimsonTree_07.schem deleted file mode 100644 index d6c877e309b8479ce99f6b164cc832a3d38d89af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 638 zcmV-^0)hP>iwFP!00000|HW2KYui8&9a-{9mN5yGo_gt}rzXKAAua)5LIXWCO`vUj z2*#}Cm9hEObL(lIvL`~@tQ!xp-jqGF-qzt+K^_wB5jMHQj8CR=vI2p& z9WIfnsyCa9sl%tH?8;ag{czU5j-}DiJ32fq*kg}HQQ#-fj(Ape`1r`D%4EA>CVx3t zOy&hN3r(wLI|;2e-EAnst^Xvf>ATc5>MZOawDvBAHz;V^A&mpo70nXsR*8B*l^NA6 z$|$Ye*Xr%%5Cv{^)SXDl{Z`v>v$Dau%0fdUw?X=78Yb&N!WvaxbTBRMfwbG<= zs48TFLo7lvIYwQj?XVBAFSSpz>$U@P5}Y`M!a!`r0_ElTz5o09*%6@zP=o3R+ofY7 zm~DW0O?k)y9{>yk>aU3)si%2c1M3|XWFWm|D9cF-fWH0y{N#&N=?2^zTnw17GL=gE z==kVm%YwaVg89QMj|eB=V`x%;$b9Nf`e*&gXv9PvDwmu|UsPItzfPyq@A4V}s;sb9 z%(I~Uo{TB%oyWI_myW}1Mm&vTKcRThZnaJh+o!L{$?<8VTmsx>1K~ow1eG_G4P60E zX}*;;q_+_vIZjALgd}l(MM?7dvj&xOFR(|F$GjpQ6@nVj7)vgj6Gxm8#vxwD^UP;+ zHVy;7=iYyG3B4hoSlGG0l~KrmZZXWEzD;i^2}MWn2|e%wF5HeoypSG*!6dcs1@Ftt4R#gQves+nh^8>auP--AlbpH3T zkm2!g(oxz025q1n!+C4~NPc@}V;6819^YMft`9tV&w%F##yHCT3^+EYv-^ijd_tCE1ys`Oj}><47?84Cs9_Z9ZXA zm>P7CxE~151prNh{#zzl7Fj+ufPUbTjWF7VHg0kN^8N4EpD$45*z=vy{zyogtLWQn z8*8uEDGb65;f@a-CN0mOz%qBo!sYgKxHFudp9|Rs{lFI(3tQvs$MyB~FZ>Jv)2hHI z*HLJHWD^d9y~tfXuq+74V+goqg=Ehd2+=9-r7)cwRv_OBy#n_fG zz~y`Ffzd&TWLW{1Arj*J35)Raw*mT|-Jkmy&C+G(j%YXO`Sy!iVS>FY}n` zi%HF&G-rXB7)tkQ_Lb79GBB9?wb6Bvq0`jf67-}&zJVD#hiez1l}}TU*aYB zjyE3jQ5xY`6D>paR-JlWRF;pr)~VxnWf=v;0cd#bL_qaNYT zbEjPmp6BW=mp1p$E}1XShT*poGw3{{YuZ0OTu*~%xp-jvo;Pu#hX)^44L&c@xXG3E zp&R|^Fl_S;?$(I1BBtJeQD*wY_)SAN9B99nUQ`QP*`w@?sbW}&L&bc3y#zt=O zs-oTh$kNovt!{1pLs*E=){A~>btAnee2TADCs|~DNi{Ap$(OpR438uL1g2G_!d+hs zBa6h8LabC&ii4Rk=VA`l*A?7=&bg}3ju};&>J(IjnVpJlXH?Ic?QEgyTH1)B#(J<6 Y{T>|l2|_@to_guMfT%!FA#p)P>Y*)2mEeF7 zTAX#f!m*LP5#<5;2z`jYN~OL^ua(*Z-F3Xqu5Ij?pb*iK67THKH}lPW-XOXFUFdwa zT{6Io+9tG*Nf^<<2LRKAg?E_YbY}Ui3)V-%aF5S6p~)8sfbs3`*PojlIfLvthxKSfGCY$ivJD!T~H3zfXPQ4li~uhL@L=EkNrqq43!0zsfWXpz?5-U;4z(%vlq{Z)vUt6-v9Yzbv;7utZEg$X2A~t#V=gQ; zp!J^6zRTblAAaC5`0R>E$#EjWL`poJKjIL7{_a9+-wW(Zo=^J-dxSA!!s3AEWrx^d zyO@R$(|9$WvqL(F0{`5-xpFZ%#b0Q&cXP&f873U(w>iYO$tl4R+jFoNkySqknVXEG zV93WT9Fo;*-h|K#`hC(%7Ox3EBi00X!-SJk@E9bIoj?_X$HTLcM9R_C)Z<8X(3>W_ z9HaY$xgO(LGn$_ zLP~=XcaXU4ijGcZhKv%M*{M=P{RmSTN&+TwM+rkib*E~eR$z`~bc=QrJF~Nr+pRYH zEFVjyufjFOYEtmz`%d}~P!|QFgp=9Od!*}kf++gyHFifrK&lwB^i%%%c+>H3K{b6v z_}O)I@9;`BV*Y6{EKt~IL*_1}oGz;J(XTjB=|XAAavK_bCA^g7^zzHeLi^1{nT#!c zcNvzFXEHQ0V44hNHwMi(|HeUbYk5x|&B}{cC|zCEqbHKS{La1fx~S%c{{vZuQH4Su!#c1S|5Nn9XOvMA P$sehId=q2KWEKDbR{o}> diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_01.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_01.schem deleted file mode 100644 index 8d269dd41b24e6789149d2af04308ade00f46205..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 730 zcmb2|=3oGW|8K9y`dUYxe&%@VQ9nm->Z3lE3gJom{Ys$WQWPRPz* zAFkbvUcEh>t?c@#w^tq3P34|+GebL6?cAr8G1oT-8eM+8_u%A%_fjU!V9m^b)+wv4 zd_(^KhYugTe`e%=tPK~OcTe2o_0m~cJGYtrFHJS>o1D1l()>$zewd!URPe!~@16ao zqf0gal?lzSz9{>PN2`rk{-`c!6rdeOeB`67FpWp}T5c=DU%>Lgp~ zSNp zvDFrDSY6$GW=2lcu5;%uc0cU3zI9Pof5zIUCyz-!4PJZiX-O#1J1*!J$48Tl3U`dkh5YeEg8_usQ!7!8pQZRy5{@u_4mIvUogG9fxTt%{q@>m|A+vnj-ICj? zyPFT(tUtK8zUhBs_Z3vLdnSvR>1+5XlET}n;6H{1Q??A+TTnO;AZ-M;ReIw{Zh zReoul%1sZKEYq2l%X+NjXWq{AdOX!kYt!O=Kgxo-E9C8KEdQiz&PlD5TI=4K8I!W} zYeivdVP)pRXP|lN1S#9qP z_6I+H{CNG-#k`Not1{yHvOE4q@ov2nXZ^D@QMqq&-l9wMSMB_qZTk7{xyu$+KaQwP z*PMCJOX>3T>R_Qg7d+SJr`(I%x8?Sf#i^l%F&V*OsoKB7*rxXTa$dW%#(IP5RrAnQ zy%$Yq%HF!PYiHNlRbEAwKfT|@^|)%uze&s5P(2~_;x;Rvu5TCnVxL@na&bne>nf|; z&ocbCThBDSxuG;+{tuh8RkpHQlc$R9;+ePVs2C>}a=y-5WAXo_+HGn1n={t*Jw5q~V`<%`Ltald-{F|| zHt6}D=a$cx-?_|?w;?LD{__6=`@8?KZ}J=(hNJ=c2!%g$T;8DvX0g9 zMddqIp|*Ei41Y8m-|yMao+z+GtR*hu;{S!4|NlKOPhcJMf$f?xtV}yL9k{ty{qNg1 z_0`|Y^*%JO`j>CPKVet=S2mUdQ$tO{ul`JUfBzk;>rBG=v5fvPORzf03 zQB=*Y$Gg^g*O5IT^xUt&mq6kpaO6vI3gb+w}rK?@s<=E&%$|Yqyh;Q5t?}y58^UlqmS=MzD--d+74t>|7wDI{TqEW@IR;5N9XoB z=?Gx-=WnRb_?A9@=lZ1T{PzIBzenGG$9nm9zyGJc0RR9100000000000000000000 z00000000000000000000000000000000000ZmLvnAC%Q>`E>0IU$|y`+5RgNb0h*9 Gu>b&sm8KK` diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_04.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_04.schem deleted file mode 100644 index dc7eec003cc7ad87dcc0d3efa1284c924fc0a0d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 906 zcmV;519ki#iwFP!00000|Lxk#Zrer_fZ=0O7Deikth#Mi-NX*!x=Gpq+87PmMcV`@ zP!}+QfRaWsAxbpJaheLU(&4SF*7Gc1$!)b>Bx`kiI(alXT`m*d zm+q51F>N*l^V2&&KmXI-n*9V z*Ynx^uS{_9Wcx6g>0|lKlkRsaSsZJ*V;+8Qma(litNFZ8+Gb@k`pZtT_mOlD(rmgk zDFsjCRB5ez8Lmuf(>$K*M{$x%`AiPqw9Vpt`8lOz4|M2#nz*4gUqICgTUF}G^TOD1QZkPW}>%eNcCrz-wc6-TqA z(6L!){BY;QUMYoEYhClo+i2Z((eAg47F_D7mvJuhXX&*|cCTHsVY_Ji?V?4#>^m9J}m+kTxw2StjU9@4lXkWC8Hj<#+X=9P!e8&P`vsLU{H;4CTK=EH-IbcZ@>pFa@C&L|5@m| z&reSVgP^v)qw1zELS(1AlIWWRyzMH?ruie2VK}PmHn9hO(}PIL%G%fKeD$V;teDo7(iENb0-o(u1gee=vf;xjz6mTI75KTpq6PUjOBM zNBx!I9S67#KMLzV0E`;yUu^wLeRuiUfb$=y<31dl_6M=uju2ISy7mX>`va-YcgOmJ zAZYl6Q~%ur0000000000000000000000000000000000000000000000000000000 g007_`gfcuxv+45jS|>VDMRd{rH=|V>^vAIP0DJbyumAu6 diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_05.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_05.schem deleted file mode 100644 index bcc6360f6369565ce70f00ce7541f41659038011..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1250 zcmbu<{Xf$Q0KoC@>wRvWirW>5Ee-Tevo`QiO1eC)_Zz`yCG6mFW(-<-pt?kaZP zO*uuE9?UWyfXIQuIG*`vW`^Ve{XIwQw#ZLNx-R?vSHRgq zMx5kv8%Ym&x-BCMStTN59TPmY549wCc)B|^aQ!VZdpw5hhc^0IqWF4y=G-?2K5SI* zkIQF_C)~)1ZF@Kg>;yJLu#1r|@kYnzy{%-S|L*Vax6?Ep=pTF%a$dbuuT9OtU}N9u zJC^eH4){lS_Jnee=$pRkuq&!Kjd+J)9jw_dE;cvlUTeLga^NwiN~RFf5v5*|#+smY zu~A`XdFbI{wEi?-aA)?Rbu~Z##&9Xq(LTSIR$4XJhYVzFQ{CSD65)Qlno*7O%tgS- zHxT<0W3r|4w<#`i+QCW6BSmitOl94pEPAK%bDdRPOnP1YI)m+^lUf*}$>Qk}@hlFB z3?dAcO#Ga7+L_9+dRT(<^lqge=gRi9yC1@N2~XnS8uPn{Bf=42bS9!;C?`4oJ-%*X zKJW5~K652n<j5LQv3RM9oOqyYi#)sfn15CCMCr)kU1QSix!L0omHSKE;A!Wp?sKM#L2)%rJssM*E|Ya^?0Hx!TtpNPyaYMGZ+&R2E^7{+d=tS=`T zh3$~}1sVj^c6~h7=zvvvRgYuh2s;%{<$~F3;3(gL2cVAh7yC^vHCE~JCNg7}v18LQ3%id&J(HD2eWrH;~zi%bbr8|Atc#RD zCdcLAM(E9El=s1IT?k=~DBf5WW%J;hu+#9%{N37bEbUJ?y@dqw5_LfWlevC^e+7M< zD0qpaQysSqkIF4Yy>GK zelDG0D3< CMrPdr diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_06.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_06.schem deleted file mode 100644 index 07f971f8413acaebf835a1c6459fa7a589dc0d08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1427 zcmbW1{Xf$Q0LQ;GDP`t$=@iFToyRPoL!=0ef}yMaRycU*&eG%9j%`XPD!!{0=N!c=jGfydJt$Lat>?be<2@`7U@r}1 zr@J?9-+-g0!=ta>vDzdNO!p@DxJ%b3jv`L?5eKRF!P=TD`Z-MH21NHiKk2d7!$2T0XQ$xRBVn=j2IDU$DGJvlp! zv+6@trJBi{Y?kGMWE=UEe|VQ+XbXys!30^L&`tH7R=RoIAtLIVTd^;w^K>ZZ$jRH{ zpu=eSotd#ZJi{+HFrxWpXMU88WqNw;C%%a;w{~QlKtBm0Z2UC4mwsD6Rw8Q6J8z$+ z{tNxoXh5cJ&^&G2tYUWbec1yF+w&_O6ER>n#$u}Qn|m7jJYmE`iaD&{daU9*U30o?#A5 z|6H6mT6QkpFWUl731wm$DlrYloZ5#$qsS17MHoedsl)SroKT8IFeS7C)4-42!N{sP z63o)Rg_0r?w}fnjYs9eV9fAaw{g~bH(-0s*&SGrZkV>^ll^i&*x_OpC>R$wirL>WG z5=?>s!2K`!Vrh#IZ!Cq!hXUvG4Z%&w4{65{^hrOAK202*Crw#)13=hMmPLETTSWPh z_l&=u)_sTMN!aD}jYd$Q8pU+kks<5RSs`2;bRnu}DBGr5s~UYfK#z43u2hrI41{-X z)NTHkGUZjMw`bR&5O{ofU55+P>oa?+X`uV7*tAk>DNUy_dE$!8K1n2%u0cCGWpX1r z|1oqxqZ<#_D8UR{%SK$!6=bX6;Q{CVoXGhjl~e&!K*n{fWkypzO61}GYC%VYqsQM$ z;btj$z1v-HUxS{%J}BH0%2Y0oH25-0@PhS@ADeW}Doycu^?5&btqJt(4qiEsyre@8 z(|6#>I@{%tOM4vJ+hEG~0Xi!z1=ZGE>Ju zm^fF^W1Ap45CR3c*l7-Vfuad#zKGRIspxk9Kd>obmS5tV#Iu z7wSGq!iCSXmw2u#AG{A%6nMsT0p^e$+`2d|)z{j-BCLyhQO5#@!Jl}VUDMW{L2K{# znje_-uU<5W*mK-41XJATr7_%0J|nPH9-kVk#OfdAa(4^i&u`!D_s^kHEs-@kt5m=| zX*nKg>?+qLX(Y}`HoL%!Fo#FNF*?8}K4@wyf-eOKF4NlbRGZ%ta739p%iVE-Vp5Xt zc)!MeRKM5oY*&5w9$R!VZzu5Gjnf)j;cR{Fvv4a@and%gZC4_himkp**hT9f{DL@d zW$;QfnhwDkM!&?qZ+i~!hCkgMGS-t5-yDKO&q%gDLHb_# z^i1lI0loQBEN*&GKlr2}xu!jFnM78KgO=`;wITJ@CyL@`K5U diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_07.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_07.schem deleted file mode 100644 index 7ca61aabe808d3a515979c96759e1a169971a96d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1705 zcmV;a23GkWiwFP!00000|LvU5Z`4K<$H%*y#GbJYryhD~?*s&;K#PAW}ghT=C~0|`ya_oI+q{NB9zvGaX19xrI6 zw2FQlALK__Rg8yf>!*CNEXui3YB*HptE|daSFS$QD*QgLvWdMqQX^~3l?s0U?a!}2 zwo#sxi`nGiq^QEvDCxZ$w{P6Kd4128x_MR!yhNyrNdw7X3|y z=vAxD2eWc~Xq&Y4IGg2Fl@C>NWScf#WYg+?RxA|v@SMXZSvc28v#c{r~S5{SdwEf%;{9;z_@8^@9X*OHtRCq8{KMRGY`Fx!3 z=Gn4xuG{l+Q6215i<5kNS)SOp?s6;U_Vc_c^VH3T+nsHz%44}`pd*7pF)B_5? z@z&va4A%8~b8UDI^M;LIh2I*gZ-nr(Jo_bI?oNtjmD!4R?rk5_99~xWyz;c0qJ{mU z-R&1G>KE-!zi7H&w9orRGyS67?iVfY7wxlt(UN}AZuN_nsx3~nnNoi!(2pPpI_-mX z4(sf^GywoEBtbA*V_&21U~q=50RWsS>*Tw3eLust007=AYnSg}qtiZs{sFkOtk?hh zX$Jt{9N6qOV}B38|Co2azwbW(0{~oL-ue6<8vp<EEl)QBZ${*L1bOgYmp? z;qO61NBZa*J?%!W%Y{IG0Ov*2G5u5%$X(E-3uarsRb^_3GIPbYTHCfnPCsO_V-$Py19jUDvK>bGW75_f+T`o~5bXzp z#w&TpDoulSCrH;^139Lx@`eMD!zc*aW;?dYmaTw>AeGV&SFp+t z(g=~sxcneXoKr972OnXy_T~Y6u+ZG{?Jb>|*Ei4fA~Q*hCaEyVocygaJo@MCLCOj? zNCkp9;RMi#)y2){Y`Iygo zw)WAw9-VrDZZ~k1A4t9)eVaz#Vggikc<=e3rUc-ZKYc3EfA!=NTz=N99VYJB9)x@c zK$FvVfDrNK)vev_1Y+|8IW#hCL#JQ%_BP@JN=XATJ5U@kLs`|+lMA+s=M&o9*YE+o z^!$PM-*q3PQE2i5Wta9qM(*yb*~YY?PPy>Sb3mz))&_-x^P=ncIAqmzc+lFz2UMQ& zpctBAC-5QE3jhEB0000000000000000000000000000000000000000 z000000000000000000000001R@rhLOU{;O~AI__yD)MFXU&#Lh%xmx;$guzbOG9P6 diff --git a/src/main/resources/schematics/trees/dark_oak/DarkOakTree_08.schem b/src/main/resources/schematics/trees/dark_oak/DarkOakTree_08.schem deleted file mode 100644 index 1c7d74527301df69d79b5f1459711622cae28e59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1803 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 diff --git a/src/main/resources/schematics/trees/jungle/JungleTree_01.schem b/src/main/resources/schematics/trees/jungle/JungleTree_01.schem deleted file mode 100644 index 329d7999e9b9bf0086bc1340bcf68ec813e9c330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 603 zcmb2|=3oGW|8K8d%(@&PbNpldtTk_~o|QeFt5CJGG{p3Lm{u}{Y~EbOmbr0q-3?>o0?6)kRLdACIDqh0;4UtgBKE}9~qxkX;`w(a|( zlRmX4$D@m4*{T=c=k*Ui`A4+(Nd$H+IrD$HY4kq6cel zrKhLgU$uDmk@bWMpyt{GF#m7Kg5~(A&B;Xs65l)t*;3s=wW9!hc*Z=y|}>TTyRM ze>&pI%`JL6HkE7U-IHrSZ`^)a$Mfs#%QpReSJ{L4?B?wdIzRm{x9Q5T_obCGGcT>u zxzarMYx&i)Q^L~A61rE$*_Zxzn)u9f(PGCnmx99gM)_9dn!bK&{<3s^))pB z6|313Vbd$bpO>Eg#}E@)JT2cKy?(+kaUMu2AelU{b+g9l>P)%5@5b3{E~dVJ P{)1nmbNple>}{Ub%d9Jf_q=DH9el-6|8OIFDRZH9kD~u%_1v6U z9-pqfyt&%_KhJ;0^^fA8rrNz+7W{0IkBdz8{sk(vbv2)IPnj=Kzcl%sas3kYQ!e|I z->p0MNyFFWNMXvmg+{NApE%^`zeoMniUWPymZYk0^>}=E_xbnsH>d11>26-DV016@ z)YfY4*V@^uPc!e%t}Hz}`SsBz4as*$PPJs3O=O+Rv)HA7S#$5f5}Up=b4s>tKG7$t zdc*(uix)4v3%5-Fu}}N`+`G>`U$2~%v2&m0?_GOj+a}7d{INVM?@!d(MfMc}Y~}B7 z7i4->>vNs)$TMB~*~9(t#EGueziz}Wlh9qobvS1BBCY6)*OP(^-*~1Q`~BUhd-87J zMl&~SBm0d@f6nAO+ty_?^Y5hD%aiq&pES6p&v)76MtJ_BJ^#NQ(95*=^ykd10QG(M zu4|dsPWJs8G$}3Z3V-OF*Gp~Y-uis=!@|GXn=EAcN(+oCSG=8eGjrnIl9cT_zbnoc z{?Peg@mpn;(w&_SKU%f(EjN{_*<5?I_1yPn4f9iR%l2>kvg${7@nYd(znqCz*J*s2 zV;%EeujG|T)$X{3d2`o%t%{7ByTwE0}rl!GWt+ zA22e+g!Y~iXLvsU3gd(K*`a4{zssM#{)NdY@g2F>wf$GjVEa)0bnWG@cRtqf=^ diff --git a/src/main/resources/schematics/trees/jungle/JungleTree_03.schem b/src/main/resources/schematics/trees/jungle/JungleTree_03.schem deleted file mode 100644 index 194e268820dfe91b25e32d8aaa2025333aa928d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 809 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 z0000000000000000000000000000000000000000000000001Py-+%b>7;jdG|rPe RQCWL&{2u^RGoDSd0041A6XXB@ diff --git a/src/main/resources/schematics/trees/jungle/JungleTree_07.schem b/src/main/resources/schematics/trees/jungle/JungleTree_07.schem deleted file mode 100644 index 85fae7c1a0539bca0d2eab69d4e1193f0cad8fd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1254 zcmbW%{Xf$Q0KoAtyFArvC?QYV?OdG4qQcRG3MVzC=mG6+(i2-radBE_sjGQNc^WOR z85OUwmNpODEM}Qf!bTWd8^^Gr#>{5B*X!;-xX%yoKj8zxnE?N$mlHmqM?)>T?v;?u zqpHcB)AjrMoDz&v`gfp!_*}bbBFfO}2>9-?Py9nj4_wYKJLN5uPZ1(wL0?-`=JoX2 zLnW|lcYVGLHt5+lC2r>OLN^B+rg`2f(2=l4w7mWP zPD}@N_}=m-1NnC59aKAbWMw71_hUyVaRe9sVt$K_@`^?xk)*E;^W%GBxyu<0qiFVW z?`E3$09(PHR&k)pu!wPH)655~SlM`MUiN&%AJw-!`?7R{4C`5!MeJzbMl@di8ZnmF zlEj=*B^29IUWZ}ILs!3fS6seFa`hkZ5+GK8D0Z&fuV_j0EL&NdXo0|89lJH9s`0~J z*uqr>%4#Wp4Z>;G4BU^Qx+&%5a6}&H23hMb+xob6#6IltIe$hjskUuSLpmjA2?~%t z4~#&93emCF@Qh2zS4`;_P`iB5O8EKNiRfdajkEa0*O2bT%Q<^mcQ_pko`7gTf-L5c z3dG89t@R|e#1lz7{X*aAgSlO-ZSX(jM^(e)s`H+m_w8H)z_zh8NdZ!7DR5VQ9|$9% zt?VCFrEpz$;c~T?sD+~;$ysqdCC57lgSW@l!{|?~8Yiu(OU-4Tk*Bl)?lq_Qbc*fi zoQ|}x2V{Oj-(2#;pa*w3LR_Z(h7>6|oz27Qsy}1LYcg7;>Vxb)LObj|aUmU zwq_cLP-;uc|rx;P6{#Ex4q3hu^a@TeLjl}ni)mz<`#NDh^$@-%`$%@uW{o| zu7nu^Vmhp3K2Zvh8Qj+AqJqzUT{)}MU;xE7S~=8aWT@WA1zoa%-O9ZH1n6#RcwjLm zPi|r+F&dLpd(nd63TnMx_Gf7dVxg%90LDt@lF*wJiNk3rK7PO-@ipzq;YQc5$p1w~@&7;s#yCgReHnY^)`x$-)F36eXXubZTmIe+1WWaD>_Os-Bh;|nej#U)RJ zue#tV@8=xf64;E?Z)vVcX^6^&Mk|LVUk1U(04JqX{7A=_K^Ixi>PgvwjKJ0g#3Kr8 zf^SK?dPc=RZ7)vb-Td34u+mux9bW0hvl7>RG1>Z;0q>xjhs}EL*gg+o4ic_WN5l1r z)(xLBcFM>E&Y0k3tcW8D&xnyauA6#=8A@(fyWC7E>z5tM;26H4L8pvkyqw}F>7p}= z-JIgYd0)4pVpSDBQ(2e2WAkT{K{K`J@QqFk&g{Px{Xcc6@(HK>D)HRKCwIbMNYofV Om7ifMI6txx0R90J?t6#; diff --git a/src/main/resources/schematics/trees/jungle/JungleTree_08.schem b/src/main/resources/schematics/trees/jungle/JungleTree_08.schem deleted file mode 100644 index 7f318e6e46e4df8bffb4481ec271e6d65389bd59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1390 zcmV-!1(Et6iwFP!00000|LvRIP8&xQ$A?`5wprFTyEq?GrQQcnXqu#r#0?5kFWM$j z)#L(2)nbqB4aSbV>yTde75W%`jP_aj2<;u6*|kd$o^#e-3<>`KB!tCPmPW|3F5F}i`7?h5QY7o5W=>_+Vda|{(n+^Qx?S^WE?cr(}F0dL@C75@4x-^ z_l~mixCxQ51d*PNb;pCx33gXtlyd7<51K zrhd_z2aTg|Tg}JcDC5Es%df*`+!0?|V&yv-wmY$Spgw-Ds?kriSFKhgW9^ml=pS83 z{rf8Y01y$&)yDu?XLW#C3_8dD1#{8 ziw8rw)$0%9&R#3%Msh3a57p=7@u}+OLAT#-%jPKYRH`D$$2ZFKk}bZtWc#$=Zyp98 z!f5YToyW4p!Fitd>Qh>8YA>xgsVuEGeLQ?Ou|7M4txJtC*eclK)eQYIGR1Vv@|2EF zYPEJi+gh>3^PC+xHnqyMne0cd%|%0-$&NI9IVD4zsh8DYsyt(x$uD}`p=%e~JTl&u z!Fw4UHp3_mR5g3MTkmufh+^4`4PB{iIkUbqsmTkCUeDs!SK0h>v)Fo?%~mCgttZ)R zRkPT7oXu9v7W>Tm_L0OUyl&$cVk3)FUuSd5%VO(MHd}u7QQ%T6;e@|Ag~FybUY{}g zO%TN$rVnQEbNs#E&EO^@-m$5B)5Sij#rMT(GFJ+O_(LoVOBR`IDYy(w`qVA~06q;e z=%2H2b2ibAgR0!ai@R0JfVNE;M}bK zDFIuf^}Wf4ts($po&)*^3r?9%URtBmEn5GCz#a8dAmW4Ntczs)pPa{a^b9~tkeDKH z@xdmS7c9^}xM?a?CPR(PP~BiMSPrcblLkEBr;2&L>pGTM{=VzD?j|+@@@MDc}h*K4=*lGiZ5uKbaofw;ZaWsuAQ0Mnf!7s8T*{xW@G#^bKZ*nR#<>Co_7fWQz(A+~_x2 zz-NF8W0p2!)~`MuApEpaH|RK**-rpIE4cVTXYlm_8=YZlkETW|nDDZs_wP*lURH(c z`olH&2>`fynCn9&YX4quUl_M2JU>k{WvHe16U?aflSj_q=~GnzKpy<${NJd5Zh68i zZ<6VcE^mw=qh&*#brb+F8|?URzMaS{Z_;H0v2UoCH}ovPOJ)(wD#>BaSD9IasA7{4 zEG}#SV9K*M^m1*I^0Nr$`V=-F!D2&?j@%w3H~jis1stqCnZpHaK7vK&3U1h#Go)vf z^dTl!0p}o<3INCjqc7$L9e)Smx~?z*00000000000000000000000000000000000 w00000000000000000000fZI$-tnYXGjpKt}9LAxH%A@E10i84P_ldOt0Jtcv{{R30 diff --git a/src/main/resources/schematics/trees/jungle/JungleTree_09.schem b/src/main/resources/schematics/trees/jungle/JungleTree_09.schem deleted file mode 100644 index 71ee63ac8626622d9b27bbc3676c3f4c794e319b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1217 zcmV;y1U~y8iwFP!00000|LvU1Zrer_h7ToKA}PsMF6g2_f$n1maV||0z#Ah5x@emK zMU#bL6tpyw7}H`Pj*@iUSLkE(F@n5Om+lPdOAMLkjHD#Bt^Wf8D?Bs5jwJpwoHLX{ zqL6~$x9|{^#!EHM;&enr)hacgMLIe?(%B2i|3&GjXO7mWW`c^y`}?ntJKq|W zSLrzE?et>ppQ)@qzWdW zteaGSK90>Xn}Ycl9335fuz$@{ob<}V>3%-devJ+lt!?N3-FV*b$K5zeKFQ{r^V!_# z?my}G9{p&f1@$ zJuoRXUPOt~T2-liXj1ErqrQF`#be4}$#-YRUQb4YME&tDO?$i1`#9VBbI&yKs`UF* zo4y)#)vk&%z15GBOs$X7u^w*e@kFg>>BKxN4!<*>#W+a^1Jye@qD&v#&T3bx^pexg z^$O{tw}W>6aIZK~cFx4#nnw-^Pu^UpRddjq&hWh0$%|f@Q7g}}B(Hjj7W>APXf@7h z9e)^!ir%Vhw-;wRG9SyuogPYc>U{L=27u>%#!^kpa2tm73rDyY)ztBGCiyL#k2V9z`;x}gy zcO6|%#cv63aLFj)l=y9Gz~O zeh1(hBluhLzcf75W=aMC@KssV|3V!82jJ=g{{yhZcq@EZh}SRx0B~uk*ZH{MZx8+l z;OZjyzpuLD=+~A#;6T#)ptg&3=;sfThQ8@!^1PQ0FFEGktr^G~M8C5DcXC zgYh?4AAr9BSe%f*kT%mS*C3y!%Yt@&z)jP+|2Z3@J_x}7O2EwjUZJph&yp?=l!h5i9Q@dw1#-^{wfT>T5%gW~6Dk`rb|8!div(_btkI7%%ejC&vPz z)*+Qb0Jwy-*&Enwflu7Jr~jA4;SX}HL+EDz}w;ub=1omsTN_c_k~FS|h}zCx)#iUeS}5gZWk z9t&;@7ZJk#8vx)7;`1w9-z*}S2KHNcT!SFL#4aB~Oq}g+8n9v0Kw6;#0Om)W|HUE$ z+d%TSSb+lo0000000000000000000000000000000000000000000000000000000 f*Pnpe+ez9z*cs_q$0|Gdh0Ff{X(K?9$g=z@eP*w234eI15T5c%f_g{8@*MJDhyL0^{CW{{?(22caNO? zI0=r`klZ(ZETj}RHBQvfg1#ThSANu|Y)`(w*!JU~*NMdsq38wTQ242A$^Jm>^pd{p zwn9Hn#ZH^ynb_xTW(%(C>DU#U4klv9!v9&MhwUhpzUrm*a_1-g3r?lz$(%!`W33j6 z^`clTr%0?%ip6qm`uHMNkOYZ;=yycx`A+BuDpgw9@zOP`HhpBQsgIqmSSvET=f#HS z6^Zp}u~=(GVl|7!s@wE-j+81kryJbwBnzx-uFo+0i>CHy_HQt|I8W^HE^f{lyindg z$T@z^iR1S$XHM^pYU;u0ijTO-KdQLJMI2?x;~9>?%xA^zoKZBdkKzfr*`?Gc`it(5 z70Xy`amD5eTsapX0B#VMpM3tC+uO*0fZMBF{^R-%aF>&k^w|J;6}E+#eYUd&++U;Yv@_IEE(x)_DbE zF~Fh}%CX#wRFRo)j6a_FGvxZkM)NqX7k{La6;6@N5V?n$C*&UAD`kLWyR*vzX%4jwH+c_ovJYfn>d zt^oi50000000000000000000000000000000000000000000000000000000mZ3(C a{WuAZ4|+0^kw{1XV*fvS1Y}jHvj70m09bYa diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_01.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_01.schem deleted file mode 100644 index 0beb23dd89d6044940bc07e44411ed98511b62e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 zcmb2|=3oGW|8H-c&b#a&((rKdjlxSIOJg^-*6L?zt_aYa-Nhy5psje(HFRO8t)E+A zPKtk2#AhxWA))Mht)jDy^rjcB%*p3k`(wXGk&*H9A4g8jiFn!lCG}mU#kJs6-xr?W zThDP%UaIt{(&&Fc$cR;b&JlY22{TB-Y_}k;gP9V?uK`63#;B_ zP`b2rXV=?pMmy)!PKth>96Yt})0>F>HYYc|2-s_O^ugJQ(N{jD{A>!XoObikha<6X zZBDu=%km~o71`C3QT^#fQ-{mym5I<3C?QTVyvYp0cHiFdF}mDF0y7^8H4{Y!?mpYGpY_2ZRv z!vRx)^{W_J9DVB<3Np3KpI`qMu=CJot?%W>?<$7ecidrIqAkwwBYe-U`Og>ZvS*3= r`fdGN2S@^;8rg9F?8!rZlUgU=y&{!ea`C^}KUTZl?>)1>GcW)Efe|TW diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_02.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_02.schem deleted file mode 100644 index fba49146c60c1abb293e591e7d74d374ca3f7d47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 914 zcmb2|=3oGW|8H-c&b#d<((q7u;*<=n%uTP%?jGlR6f=u^LGQ%{v0Ho_7_xibNs9!k;v z>E2ttO6|B){xq+ey+_Mcg(Kxt=&H+`r>#CO+_^v4YVPc70Zx}!y0Gp4`|J2>d*3PP zp|NM=&lbz>@BDOguk`l(H}~#X8f-4hdv~npDBp?GM<**?jSOA?WR;e9<;rR9?EL(F zeEh|>6E8ltGS4+*{VY6p&YVAa^|Mvu&Sy>f{5gqpzkj&bx;I#bT zyW_i7h;f_FPs_Zx;(6L3g$dcxX)@tgYwZ78<}cfSBk-`pzIz6%0$zGweRKY^+w!MA zzVCDPZ;*Ff9J+G(F1w)H(${%@x$eHJA9X|jR^I-M!2KEJzg?wSKRs^Cm}zAAE%er$ zbGoae^sjH;Hv83!H(z(oxPAQ88B1>achldjtvh*a?#Xq<7W3aeoAKYPY<2vm_;223 zFU8-+?a!F}z3%nB`YEv&+V-nFE|d0l{Wr<~@PXTwFC;FACSI_VKCnDXqj_PuLwIfQ z{{#Q8Ux@#A#k`=gbV_}?eZ&8d3m4_9*m;yCcUB+2V*VkJb>1(NtFq-e4AC32nLSjd zl`$7ctX==V@P(AYN4=b*bCnq^T9zG|^E7)mZ*iS^-@hAE%AVLfa9?(RO8Ju*1&*b< z_3qz~u$4d0+rI3?&h2a1H`p`yEwrtwz5dfg8Vi?sE6! zF#f*A@B25tNd0=A{yrx2@aY#Sw`Z`k#GW@hz$&)${k`1p7p|@UER}cK{L7t7&6hu4 z|5zh<er%;2mTcEph4=_C)c3*m~blt2c QTmH&VQ&Z4Y`N_Zl0D1Mi9smFU diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_03.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_03.schem deleted file mode 100644 index 3a3d1c857545305366f02e646a84a00ee2936cc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 959 zcmb2|=3oGW|8H+N=1aSZ9Qc^N-q<=@J<~tt;k_kU8cVkX=?R8fGOTTJnXrf}_szz= zU2k1?PhK|XCs&QiKF0W^;yYSq=6ZHZozF3N!0>;*$jzHKD?cB*^}X!vK9BfK>)V2j z&*vKYsQkSZQW~h|qT-qIAv5oDx3c;CudelpId(hFJVcW?jx?v32;$lc}Nw%qxkF4BGSa%A4|RMY9R(+oAm(yFTdeE9L@$&wAs zf&Qxw$$r+{VP9HT_iu8w?_Ht&=U4rHf9~?7-$wJLcNSjwv-@|7jb59pUA6Y?@Be0; zUHkB3^T!wW)b)0hq(0stadTzO1<|iFHPwQ(f4_VCJWsOzO1i1s^<%%*#JKJM<@xUG z^nELuBQ0$THJAE-p1JXw$|lqDmAtEKa^#=6s-E@B+iPidUVQSZn!J5$PhGm^p8l?Q zP1@6xB)8v>Yr3!K5@6h8Oy~| zQYHsCFxdYrW+YelR;MS}M;o;pKr1 z^1-LgS288XN&RVP$kvOvKWSA!D+HU7c)Kb zUfPz^fzy_+D9QJ3w__HSnQGSWabVYyhqq^QO=(*m%gzz=;8uBq1IyAcWgGroes=EO zeHoU`6B4GfP7d(p=FV9(Q@Y~J#mFC$8vi<2)_5=ZQ~R-HUHsKMw#<*Ug5*}N-!Ik_ zx$L3TXHT8A@2|WKV134aBwwNCKmSv;iFX=HyJ~7n|0))hbAMxxk9+vZLz!P z)@vD72^IYhwq!`Q!bml-CJieExE}If>lC-{a-~^ZIXC Knsoms0|Nln@Xsm$ diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_04.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_04.schem deleted file mode 100644 index 4e90c4f75eed132a40d732ea14424646d75c8b33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1067 zcmbu;{WF^f0KoB(PBXSLLFXK)O=o7f+19noTapUllMNYYNg04!qZMlr4Y1wQNhsNUHuU@U5k7_ zp`7sLumf_w&Q{*8b5qKv#)L)Ht*yg$!J6S&37T1`MJFdG(~qU;r8Ark`QfgSIh|>X z8{|Ve((Xwnqhdcz-<|fYhTxe!d7c^WSkXKnGY@`NAFjB1;!t&9)39*io|s4nciZ0u z(V~T^iT9YbGus4u<(XeiW_Em8#f^}y08btnuYCq8BW>!%T#)gm%{}|Rdaidf4ek7B zNYc7nPnPz?Le2i8@inH6-^&X>9G_$;I{NgejviTfKCEC@|iOkO~x~2lDtO;R52QzM&0%i|ilKqJy8p1=O(aw_uCk zIrT-D-jT=>B{#lQP(Djdp<++rggFNd#hhJ$SZtwn4#slARU2On{_c_Q;o-yA-GrAe zBMo0XIW&kCe}9(OPzY8pXP+ zT%ywL%SB-%6BmYUnD*}zn+rW1g<2{f<4)KP#u<2hEI9rOe`U<-rxRz2WSBSPiZw$rm3R8*=#Sy`IEs0Emj|hR4-}BvjfPA#+%Z6LvGkv?bc9GfWIO`j*&Ku5|4sbAJ;ruTY4(OcMfu^D Ua+;vK6rv7=vN!UToxouK07)enNdN!< diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_05.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_05.schem deleted file mode 100644 index c3984cbb2dbc41ed0f017f72c074532f67db7c6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1176 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 diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_09.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_09.schem deleted file mode 100644 index 91d0c2629a1a0b663bdfb8537639a737e5f78d48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmbW%dppw$0KjpVlS_r3-101>3vyj&#c`deCpj%1LKrJ@tISYKxeaHJ!w!mP4HHF# zxs>L%8M0hzOpfcemb*vh=dw0yHrsigbKb%E{_%MUUt^q_!oSG@78l8})9FiKYu4OM zEDHnB)OcS(L}-Un*V%wZeNOPLzF$w6s|-BXHYsqK^`eGu>F#=_U}>K)q3F{!WKh3H zLy{kvB@bM#)_d&9a9n;?bQWK~@Mvj?Pp%{FxYf)N82)V~mNhEM_>7>*0a9Qylrgz# z9~pXWgao=vKW>Fz-{0jKKQbbkOK>aq`7ypz#FlC}HRH$TD5nFh1#97kbl6X|$N7vF zRxtCPP;OQ<@K&UCWrU&V94*bU>(?6`13wifrQw}zNYzWhKq;cv zjy@X*G}cb|2XTT*4fZ3QSe2z-MohgWZAgtE_M9b;a=fm|57M^NS~9JbcennlQ`gR3 z+eCOe8SZSG0k_c;uFqo~3K^`$wYu^R7>?DD8s#WZS:)W;1hzx>%14D5%82vyfr zy-BeS#st;KcMU|0(~p5xS7ZjlnmMv1)uYMT+ctN!X&QR!z2|%o zkNd|yFFx*aEV)6VfRzC*NBk)rL!y+=^moUd=UTJ5$nhfNxGSJ%onPTOBU&7q1TG=b z#0>yhY>398--M7GL&y?t)e5&NZQ7b0^Ky4iCXC~-)l4RCEn2+ZmOr zv#FUnA1ddH;$sjXH+aod;uDCXN*tqJ7j#}sGcZV!B z;9UXX9X(dQ3R(lwFPp1jBVQ)rk_9R7{Ftw-#49O2OAQ@Rc`w7R17e(c$(Tw3CTd6g*uR_ltlaVtJid)XA;$6lEM7u$KhsI{ZP z4(5sVN8%^uU|+3#*&%0$%66!eD+F#C;C)**x&espxhoU)O!H7#zD|in2c}gaNlu4i zvuC12V-as&@htu%nY9Y+#Z3cyW%?!;Od95*RrNv1L z0jWty1|sQr>Ux^$cQLKar=|oyYJH>~FV}R6eG(-T*5_7K^fGWXj&kl>1@;sqiSdRk z=j=SRfSyEE=1X@LGHl_M9F5^ku=^=0lo}cwGygeaW>}*08Q6-``$-`RO&ILO4-iaK zISf}jybWJ$Q8xP~eH*mIXa2P`x^<%?H*yXWt;9B(Y59BhhYT*A#H;t%-x6im=iTh% zK!|w<0w7Ss*O0K%?J2`U#4&+`$%l>se9LDQN6tf%f-Jgw8x1ZyXTp^3#fvA)InR>O zMAOU&w@Y`@V$cNV2a09F+sn3c{}jpXoTmp514bxy@eBL?5yNIO11m>ItpZAg9}USG zL>1&)Vvv{pS{yV1Q(H;9Dyn`kB(c(3?Qukal=2hLG;6ka20a}@cMLD(OXFO`c?HV< lP4xeYY15dRrnACR`7vRE5B-~4cZI!*qe^Ch&^-l(e*jn~=#u~d diff --git a/src/main/resources/schematics/trees/mangrove/MangroveTree_10.schem b/src/main/resources/schematics/trees/mangrove/MangroveTree_10.schem deleted file mode 100644 index d687934bf7a2f3d8e63418da1f1cbd8545ddfe4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1610 zcmcJP{Xf$Q0D!+!^3o)9F6WGEU3cswADB@A5HQSnk~y~w1zR8+1=;vZ@A}&=ZEJHc+B&)fqzjil)EPFIJBd?gY~o< zzK2skyyAoO^NKA>bv?E~v+<;O(X~$(3r5bD^!hZxi7RNC>GA;o-Q&Kbez&9nNv|RD zDV1a$`@@>L=E=Eu1UqFNFBv&b3#7RVZ>}=9%!Y0^3O>T76l;IROOM3MKGi%aEoS)Y zMzQNhQzxj2b$Y^JOPE|W%)YGThGl-RjxN?JZf&w_KtIQcS8j$KdMr_#8crH#&go+7 zrf~()QSby~(p(%&$e147c>AS8a4#pP70Ww<9@sGXQ^cNMn&zu(d=Xvwr+OwnIWioc z+vr|mW^L#G^?IP zC}U?VGhtP>jMDZ+h{`QzhUGW4@zBIPqioS*@?qRqT%Ch{2S$9%DM3P!9hFv~aclEW zDkV1M_Xi8|Yh%mc{Pn}O(Y@p0u#r2ZtSDKh{!Z{9iDbu~W2j96Upf-dsgPPF zf(Oe$ORNW3p*HDzWK0_sYK$7MLLt~uVZCG5&M|A1A#DPF`E1NW5CW5lz?iSHqLm@a zMrzY31SVY>VxE_$}|f9YC^8$#MP)bFiU?H%vc}_cC;mv;lH- z^H=cv6(=qcIG~J(*Gw23DFoWt4;kus`4Acev`6liQFSct|>+i7XlGd2}s+hkgy zz3)S$*-)E>C-_n-t1<1!;r$Q-%u?40c!di#tq3T*CVuxUpU)BhXdrhmwfZ55oF{eD zG5n?8x9}_d#xmQaiu(JRtQNdel%{eZaYoWAHp@uJ1rANX3WpGb5B_k~t;Fc2;QPi4 zHFAY_p{~Lj1G?DsM(Bz28ti9V+3JRy3;5E<`W8h%^_eD*KzvYM6KOWC*g6R4TYf#^ zt}|_(Dj1coxfd_$oiyBkw~d~!tPWYvgWd`j5x0`x`SA49fbA;)U58L)g`Mh>&S8gzNv-`9ypRfngWL=Z9# z?ZVt*8Kl||glBNcgJxmpetTq;zK<+^h-6*{rf&yg>#i*OD9AkaSlNDTa%MbuawfU# zcOz&w$y&c;yQc8_xc-Q*+OBib*8b7>PG$=|rQBINkHa>_M}M?N-0k$TqcW-q^@7>c z6vGSm-@j6$^1%X4PqMEoWiGLZ`%xT4MG*-=GV%C_Z8s`|A$>D5;}>~vr53*(t5Y@0 zzH6?u@b&8(gywPknWs?28mg;I(h<*je+sOIYytNlvb)Di~)T1;c6W(cAw3;;&p*5s6s=UO)%A0ICd$mOCg-BJ7i2;+ zh_sZ)CiuUN|F8Z^wfC=Bjp>z(UbHui TwT2oqrqvp?*S#A*t^vT`M~?+f diff --git a/src/main/resources/schematics/trees/oak/OakTree_01.schem b/src/main/resources/schematics/trees/oak/OakTree_01.schem deleted file mode 100644 index ecee362323df5222221f6674b9d80fa9b3d20782..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 760 zcmb2|=3oGW|8K9I_ZM~)Iq)&O?B+iO4t4%{d$?I#7EStc$>Wh@Yl{mXui@pgZFfs< ztL_e-xcPrm{iON^_5VWtaRu8PHZrDZw@jGvUS09-x!IM*-rvvq`7Tkr`nSxVe@f@A zc~k6PnRHKD*ikW0XHTG;Z`3sY?4Zi^y{~SB#n(t*QCgZGU%&4EhDg3iChy*qC|gh;E%8cU$e-(3a{lX|MehD;^(JTU zrjx0D&-fShy*zo@lJnlH6}OFpwoiZm#-uWP-ztMMTTX_jhJUe^pHhEKCF6+a?)>Q? z_kuT?xm9nn*x;SLT+8IPmRD+xy1(9Sp{xH(=BUhF_Dd-B>tttX{mM=DcNMQ3-jGM7>F`R38?bYUUv*v0T zpE?&d5+~wJ~PTnNm;`g(h z&Frn6ac8EjsF^+g&99R5{WnyKk4~+e^H6?%tLL+xW+;fB_q^}-zTM6zYZ+#gr}N)QWGD|Rn;s!hagJ|Zc;S`$2S4|1{`Ys% z``u~Zmn8gVED(S9Yn$ks6$du)PM48o`uO&Tv^vM0tdoN0zN>wIxhUZ?(}OU+Q+*i| z_BZ9fn;_2@^K~w}EbE+nxkoehulUn#rVli}_#MC5i&gJ!UR1fVuRDBjM{{=hghgk$ z5A4;l=3_T-e{PYd%JK}yYFaQ93 CEnkcP diff --git a/src/main/resources/schematics/trees/oak/OakTree_02.schem b/src/main/resources/schematics/trees/oak/OakTree_02.schem deleted file mode 100644 index 5f041e27d115dcd80880b950d0e64628f0608c77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmb2|=3oGW|8H-c&b#a&((rJyRp;XwOxj{=Zd?wV63wVNyUR;who{Ge6#}zV9v|rv z4*xc#=}2GrgT{ytJT{!#W~s|`Tb*qAI;{VzyB69OpPO^Va`KVbiR)bW*^+HT)nr+O z?5?=Zd}b`j$<&#zwV3bxoVq(x&&lRqxiBx+>oQ-~#ABysZ& zna++8Tk=&ycGku@(G$Mu&h0zqR`u<}w%B8HOG8yXSN5(x753#G>(1xvwp`y_W3W-R z^?T^D-iwVoez}>=PO8t^ytr>xtESt}J$?1bNxymg=2tgwd1G6jb6mP{jojRTv{m~v z=jFeQvo}|pulM#wefh+vizmu`6Ph~p)=^!SO?4)EmtU>aGjGq#yppq^diu0&cTVf~ z&YL+;bKBRCoo}wjt<*a3B>FhJX?FO2qx-McI>^cRPRWqow=y*ITe|MqPltN=`%c&V z{_taUjkoo#JMpJe^R7?ZVHMw*n7e58XVq6%)OK0KFIr)C@m5XeuC1MV=cj9Yi#@h? zL;jww_L=@Q=cS+gaN2)jkH*7$Var_ZowQ$gamCCHAG}X7e6YUq)xM!#{4f8XvrP9B zE=%9q^!NU^bpHK2CSC=w=Q>TgwuQ8hH0_2!fWLq3CTVcg?0>)b*1vD}F8G-c^|VhHPe-PyV;-HGh{4_DAV}KzyJVNU5e!Z diff --git a/src/main/resources/schematics/trees/oak/OakTree_03.schem b/src/main/resources/schematics/trees/oak/OakTree_03.schem deleted file mode 100644 index 5b39b1d1c9c6eccea2aa813fa8e1af93052e8cb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1161 zcmbuun8Hd2$p6h!mXLJiR+2Id&kX3m*c z>NRD8=9N=4H4;N%G%Y+(GsOIbsF~=LK)(<&l=0o&_7e8|@p%akyuu#xZ=h}_HrL}z zw;^B%(7(?VH=*(tUo;@HJRio%?%$4qevfO5aEiY*xpg}6B@nq)`Wl(b3NC$-y@;V| z2E0aoR-?%%zAx$*!9vwUh`NqW%UKOZ()QgA7r^(&en`w%zl?tZH|3Cm6ArG?9-1*3 zZL4_7SuX@gVrQjy9=|so=`(K8O`)0KFpfSqa=dw`d3M>a?dJ7DaLe{bhnNNHDbyal z@A3E?3iaLT){$p*4KfT0^|)nwIgOf2&0{37Ilt}Y&yv1P=wd$M@p$ODX7NjYym85t zCq$sJSCS~uZKfXOR;9h1j3L#J4ozJ#&QLc^H?FUG6NmytF|lf-{AS;=bwV$awX zOZdHccy+teoNC3_o&*eJz}2W-C?e z@@DNjDI;Ves;{2?dtLIMYBCaeqGDISab?Le)>*3(PtZn;@+}=&M4~_7aZT2%*fC7T zhCr4)s?%q5=e`-D|WeGDc z)lGUGX%Bw+zgtChl z$^;)l<`b_0dpB*LfzS8yF4$Q6V}cmw$tn=HE!_{9JTxj{upMNFk0lv42>iJ0uFL1e zhQAg>>Z_^MNxkO4=|M`Ta9^UR)BzWrLl~;PUA~Mq@yGw27 zmnJ9N{E0q{Ol3U>u`A&U&%7p=wAuu4R*BwwRAUzBZVFx1*v->aeFb_NYJx$ey; zG8bNSsl0jgxzv-bU(~-3+ML_iXj0IHhh$(tNRztIy@6`G#>%kmwg|K(s3O-U-PEcI z79W17t5OcTG=O~C0+&SFnZdvUSlW(6cYv}ds8zQw;2;xX|IKo!6~J3M{GB))BsY~i z9@dZIMhH?gE161HV%VHCH}uw$&S{0_?7$sE0+u^aQ1|gs&PIBrtNXOQGq^72g3YKO z*qzlrXVDQ$n%RM&cY#LlFlup^U4Z#hloc_jK;$#cn#{u$SLzYhb_zw1O~;DND8!TA zqM#53Kvwl?eF;Dj0{HxtRW0QMIuKM$?)q;L|J%jgJwBmj)tNUK%&e>gExlW0^CP(Z K?d@rSK>h)wl2j=G diff --git a/src/main/resources/schematics/trees/oak/OakTree_04.schem b/src/main/resources/schematics/trees/oak/OakTree_04.schem deleted file mode 100644 index 2f6c9704a01d151851565b19635f63b48ae42c94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1301 zcmbu<`BxGK0KjqcNSzAKt$eRi=TW*mXs?7+@Td&6H0`hv9_36hB{jq&Lo{CcjKVR? z8l*TiJb@Aq^1!Z1rj*B!QFv660tqPyqN00m`xEy4;qxbaok$i&|F*gC?%Cz{M{SEf z{FEAV;K$v5%Oj^$9k;ou)B{C6M@=*%;1H7o=B^$dWo?#2wa#NWtkP3MSq0!Jj_)c0 zj=he$h0Z<0-Fs`rwWnnHz_EES#Jl4K0x;toA-RyyKu`Btl@UBoS z^aM`1*%4?W@z05qcOo&vq2NM;R5u$|*XuJkzNpTM;j3yT1#N5ST-Ap+(eMSRge2w3 zqK5&AQJkwM-<+5Y2@LQ`(T|G1iobqY;WnNsOBlAo3%o)$b3i^mh2!+cQCWV?5rMnx zhd$?u!+mr*8bR56|M<-qO5`+B02iHA%g`&*k=&lyD;JKc^rL7F$ds1(vi({yO(k7Q8?$hMEs=8v}(r6}lV zQw1zIY0NVmWA+23@cfCVr`1TdlDw`;qq)L-dj|kY@qS4=+G1^Y!aXGmNNET(f_&`l zpD-mu^-RBXm2JbG0H%GOgEv_t9EAz?VG?)LqymnYN=f4y#f{?Zf=c7$>kz-oL%|gr zV+tLHX2Id+SXKfU18B;l&ViWLj9NeIAzZBo!{|I_c=^3>7l}gz_51R#RwYnxJ`r)f&(|?&GvGonS zy1@DrA|^5eQP1{;*}pe|7oVGDVDJlC*GS9@Pz%kV<>l*GimoP>ay^FteM-%j6I%{r z7uqs_wW0V&WXSEZKAzXu+l{<^7-e9q?8)DK2l)`;;Dl0axNpHY)UBMJ>;cUX-sq)O zmwn>qy2Nn@+v=Kt6D_K5WFlx)reHeOov*gZC`39%B`_~cZW1iHOcz}jaG}cz(6l^N wvu%yV*1D_Co?7w$?^yrOjf=>!X?kVdr#Vh{BdIrHJA2b6Iy6>QtT!_H2WAYAb^rhX diff --git a/src/main/resources/schematics/trees/oak/OakTree_05.schem b/src/main/resources/schematics/trees/oak/OakTree_05.schem deleted file mode 100644 index 5eef2560b69e4b26088eb37704f4de4b5789c444..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1249 zcmbu&z9gGcVh;>~7Xfr6*P-I?2j2&w$hvwS=V>%bcW6;z>wb1HsOFzT|iXtC2v|6+Sc z01wO^O^(Gh_&OO{^hv(0d>IfwXTy%A-(BkMzUs(2Q=D~T+Q$+eSCPXz{+!Q#wC9F& zC>t;wSdb}E`^x#kV%~17|B^|AT*qYl@9cS*VM&X*HF~;IdG*5l*n6oM#>NFNl4$9Q zxnpl29OWJu)U$gA9g6gubm*t2%v08>#$81-p~)UCN>0IYKL7w7!*$t(8tFs%u#@Wl zc}K8HJ&1FT-MQa?cK42!A*5>ZUK8Q(X|mPut3_AtfrY9m(MR8KmwxB9X(bjoX zzMlBHt5=MfH6~Tl8Ci`y!baCMdBP+v`o#NO>EVWu#L)*{Axlm*OK|D(WZwgA&R!%HgpynB1ttP-c#Y}`fC(6wY}$5Zhb1gAa#=~ zCk$!*uq#x@gnCE!Zsw}oXTbnYN=+-OAAn(^Qojv~t*+C+}=&GZVyvI*IDj)-+-z_ zw$q;lWgG;jp^6*k{xG>KBCxB(TkBY!G0Vj+t67j3TB&;d;|BD3s)kcCk-)AdFCK~0 z1Ua=<(mg~9%MycZ)#DMRHiQ$QR|ZO-CJzD&emcYf{E!`SrH(q002?#neL$K<9~!YB z((21p(X~^&2j{3&lYGC@dcnO8blQqcrjtlH;@c*YC2!t;M%Pv(`ff~V*D1yP5i>O3 z{fi6E-__-)%#3q*+DQvWM`2V8+t)l1jADa<1+hq@v4~qGOc?nGH+&Lq$ShZN3hd`9 z$+x$TF4%Ro`88&_I%!@G|E;a>pynpYIeT_sA6H<=dGe7Jqj3h&92x6pPBJ2LYe?Y( zq)M%dj=u&`^%DmnG6QN>Vs*`2RXq{HZ&_YEn&l}5YE4Od$1gPn2^S6SfJ@hvSnUXW zBX6h!qzTB&2iD8NAR}q{Yu@F$Y-=KzIPZ-w!*#ZUetD{>Ma`;$ju(eyE8t8<3VB(!`)p>K^#z?UwZOLML%k;ah>$0RsI4i29P9 diff --git a/src/main/resources/schematics/trees/oak/OakTree_06.schem b/src/main/resources/schematics/trees/oak/OakTree_06.schem deleted file mode 100644 index 3a28103f7b5581f991813dab0b4178f979402d4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1632 zcmciC`8yK~0KoAzrNl<%cwc)Ry<|~%$@R3^RIg4~QAm=!CCBE-OPX1Z%2C!qbDK!c zX2#~ohAc(QY>RC)P(zf~ui7}NqxRT1-TWgt`Pu%0`h&{5$85e9W9EWZtfXMI;{|VDU(RV&2829439r(ug4I7=)<==V#H_6TC1ZL|UUCkd3$#VMaiNa|a%ydsyKa+v z1fwi)-Ha7#?VAl^Wn5pejQiqUM^&#_oyo4y6j@eW<76x46tTI-m#+?unpU?o9N@P# zU(k7pw5(vV1D98#T5w+Wu2||uYV1u!9UkBJLtdV1(R+f63L+xtqvBKkc=gPH8m8P6 ztpVcV2@scFekJ^1rZcCNU&LFUF2^t`S>LdSkd~w@+D3UfA{@g_lr{v-5m&=RR5Z*I z&8XRj0&`OkStTWrNR}Uh#S$|W#mj3>dY9JV=9vM#%+JjiZMHomE2b8Mzzxox1G<4Y z{#m$(eYwMp*`7tWH{&w_D-cJO!>v5lWANeDG1gh#n3Z_s36q%_N@7+6v8+8YYl+St z@y!JknoPpC&Y!i}F;cdFaD2)H0Jw!oorDbiyxsu@+`_4RGuRFXg!O4> z#Q_jL$vuiSNS-oa++2f;oK*sR3i+ZV*3HJPtKR#Zi3fMwcNyAj2ijZ2tS=%$g?CS4 zExN6qWbOs{9D71|N(xaJ8#pGY6VR6iC~gIZ4ZnY=6Q3uQdMiSWddHMG(564TRswz{ z8Fm844POk}(!X2hI~%^GvGaOQ7M@eugMb@ZFq&fH2YvNXjS<$tqJ(d7Z*q_p7da1+-~0-AEuBAiZ2P3&z^>P|9m_qDDy#V%RUf}|n8b9p*7B~JcIky$HB&n1fDNm` zMK$QgZnw8fF_^)R+GN!eDX9<5jVEhB4?bNi623Z=3mv{ZuoX*_bT%Gp&mbd==c3a_ z33@i9h?x&)^Ypst*E{V6gy$TB`R5Ld>v$ybsjbzyf>dCx?mM1s3GpI~$ItU^E4SF& z<%{5&M8796*~O$5p^Kx|Mw4}n+7(Sn6CV7K0%ZHnjf}u2nzz(bXuc<5v@|I=WP$IY z0&$Yy?J8G)w;6W z%7`R8`OhB9P`pZbclT6dDXNz4KX$4`Q;?30!n;p9R7l?zO{hVY7cGm3m-xS6()y&+ zlCMKXbZSCxx2s(doyqEGqWcJuKPin7i&GC)@$E-4|AkGPy`I!4tnw+~CQSOI7KsAxd>)aWcMwzq`md_B}~ zGhzQ(xr_*u1m=}=Ycl}r+p+FIgClC#l;M-()9iXcLTpL=i-^u*s#Fsv*Lhh- z_iWj61Z@lxIw0SjdC)=$w~O6%6#LxaO?P2Hx4dvtw)C)FsM;~dluXV8cSbTCW08mb zj_zE=IWmE31??c|NIOzaR_TH%1tivUekyMB+u(ptQNSM%H5`MEQ4@;|mJA*t|NHv? h;H@BIGg$7&GEN(%BD6ZXv~5nYrI<-x5l8@le*mg4BOm|( diff --git a/src/main/resources/schematics/trees/oak/OakTree_07.schem b/src/main/resources/schematics/trees/oak/OakTree_07.schem deleted file mode 100644 index 89d0062ce9baf2c1b60b5825bc680a461bd49004..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1543 zcmcJP{WsGK0EfR`LRT~I@)9R?O>dl1Q( zxbD?*tkpe(RdRm#NE(BAcZ0#a)oCAnK~b0HXEm8PJZG;$94sjs4BRs~T`j*zDixfu z=E3)Fu&Wo<9y&0i8T$({;8L;Zl%gJdH6MJXdpyp#+;l} zRGg}r6K>+%Z1lRxCAkha7RAwcx9J}(R(jTH9&ZCG(b$I>pnIy-^TFC2!e$xTRk>hk zk$GmADNeu)%$kst&dKGXH1k(Yt_<+*L_YaJZg!J{OPnk#*|eK>VTBr zze)P5{l)elInB1TPm~hM^&`)rLP7sb)W&L8x@FatLPb=} zMqYhjsJ@yRx$42%?;^O$trwo-79YsXUayL1a<;EZ^o^WNVPEgjjm4OaCrQe4Tw9zyTQ4%2`F5FHxMN7Ua zn)=|N1gWCSwa+}@xMZ&{iM8UUZ|bQ5@@?U&WOWFx*9>Tr848;b3*qLV_P0^PUMQ{; z`m-4goV>pYd<|2Vrp5nsWZpN8gxsyx^A~wbOlNUM%hp{0&vESP#dWg^XXFY*4X?pa zYU})wUk0`Nte(#GKE5et1%!I)20sGV7$sXSX1q$z(B-v_f27rNaQC%00D3@CwA>p* zH7K7p-Q@u!7aYB8kW>oBj6B;b%LX~A{+KT=CQ6@Y+?vCbBBfFCwJTmCHxip)yd)iB z$(kdsd6rzbT2;7kYd&Z~KfyoFP#V~DC12A}a{~HphZx28rtdy=Ks)`B336XxLCPI1 z7T&b=U<2JrWwPe}0A(Kil3$A+=tl>nbXJx23d_`L%K_ir@DJ|2FV zdN*CB84t|=O;dNDX|uC`We4_=waYOn3txiRTJaY&c!!SYHd3IyadK2-Biy|x6KD846T;#NQG5u^=ft)(!PezL0HMIv zDJD+yweYjeE#h?q`Il=+v=_zsYmbS#g2_uxC&}q@P+mP_<7PP&9ct>|U$m+5Oeom-WfcJa0hI;v%>V!Z diff --git a/src/main/resources/schematics/trees/oak/OakTree_08.schem b/src/main/resources/schematics/trees/oak/OakTree_08.schem deleted file mode 100644 index 4107bb0d14c98c38fc60f23ab880c0f36db1d529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2123 zcmcJQ`9IT*1HeB*q+Hcpa}^;TVvi$tQf|wAGx4}`ljNKuJR!%3a+e3m6wNuxIiwsj zX3ViHw=G9*+n(?1`~4ff?;qYjy?=j;Wv~PPg+VF81`$=SruwP#dZk!JH)ps~;{CA5 zO*Cl8yy0920q?D-RiZQ-H-m*U^uJ4rI$+iM80-|*vMtv>i47NRJS6- z2a)zqKV$U`(&lfQ-=c?O!x?Xd4m$4AU7$XFUme)7l=RZzjQ6kL;bcEqgxK)=+t|gR z=kl%&9N1&>VOMpaT0X09U2aFpPCtU^@IrkfY={& zN*rgLJ_Cqtx`#ceP6Ge}53d&xT9M8V_e_p`!Ndf(W%RpFqTnUxl-J?;Xy&a~63-rV z`{hT=i$hzP`5IG`0l=k=^{)eJ+bYt1f-&g{XxtG#Wi6|2924_^J#}U}52z;lY{q7@ zQ_7)CGc_?mOhMRRj0uT59O6}xjKW5Hy0TXAPKUOLwZKT;+s;U{&@`Pk5SlDFU!pdgu$sTjmc43_eXQ1Yskst({ zVNO&kFONa+g5?0{R#(R~RUBU%V~9Lv)=}e(@?y^tL>_O76N^f1I*dJjAwGKdMz{yb ztL!VcF8g0va@|a{+)ne}D zTnr7xgqzhLXyH~ybQXYohtXldnm< z&Xs*v@BMOIL?oz~V?d;nPJ-$Em}5EE=6V_l=D4jGFNEPlqgE=Zmt5 z`R+(Ni+>nMJpB9Zd?)CsBeVaOo+W*vAeY+XU=j3qR`$VTO>LISutHrU@NJ*BxVSN2 z2ZKeVu0YyOZPyOl>SdG8m23DR*X{^*7y4Yl_NZ3gm$9ZZ*AuA=@63WpS+v5kVq-7= zMsTc91R2eDMXwyKnIu}DYe78!X749uTWFZEMro5uKH!bt2M}}ES4^MXe8j&n?B<1IZGlN zFetP!6HTc?cb1}D6#F${ir@aUvm$tbU?uN_v3b{r*X4oGHV>ND_EksLV6e_vCaVRp z4T|f9O-VDlJJFHZ>+GWv6f@&frZV7v_OO{?w|$iC8=E4#?f>j@U%%YL;OH3}p!n`B zpvS9>^U3p7Dmg|&4fv4XV&MrW6<-@a2d!snkdAfgj=q@xMB1A#eKa^Cq>j1TVTy4? zn?lAv%;r&u+hkA;dPQ%{o~RaPFK{}Ufb6cnB5={TO0d!un1W^CbVgklNW@92T|mtX>kwWbT&zV5}#zLd6)7 z-BcW-{Q`tcw7}&TC&tw!Xk`V~pJr@f$lOLF2e4E9(-W25F^Km;OA=O1WSao)B+%z| z?>#FQPv!btdzZ=)g$JGDQ z3SW1g?C3gW$~l~Iv&O(K>4Y84HTIYg_s%7TJc0>4Un%4I-8&NCd2r!A;#R3y8^`PS zVvBp4f7xZ&yG0eWS?S^Fr0B1MK~zKx^SH1Wve`%>Wq4~S({#J%rP8%rb=ClGM%LZE zqXmh0K-Jtm@@w)(e#dzY$W?X5iyVGq2_5_Mfl$5yu{qgKu5B=;pY>kQ>Q2l+KL7~J z{@w^Ajhy8i1@O~ZWC%LrtiL(`H}e0d6ByFkxguFov^_TRBL~iJj!tlfSnjiXAmT9q F{0q?%7JmQ$ diff --git a/src/main/resources/schematics/trees/oak/OakTree_09.schem b/src/main/resources/schematics/trees/oak/OakTree_09.schem deleted file mode 100644 index 8fcfcedabea4e4e0002ea6568d3bf160c839632a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1938 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 H3jluu*>usu diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_01.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_01.schem deleted file mode 100644 index e38a71737325be03a5594dae29fce2940454f8fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 456 zcmV;(0XP01iwFP!00000|BaGSZqq;zhJSnQP2#F*Bo4q6ywFxkBNS07FI6LW02&F2 zN<=7{+GBea$Bw)c3Mb(v9D;jr4UE<#Z3Rf!FP`~#{?GreHv%FI-o-huNldKJd(X8s zRS7^T99)vnoih9pq5p=35)uj!T#_*0eVu*yRzw(FS9Ouh5+nV4ik_T~PekAA0kHW`gyoWw~yiqD=qvXAiEB$6Zap6ecH zJcTEJOy%VEm@U_pwF@zusnlC`v%bR1e-I0%Piz;7u|j3c)5-D1?c55N(t3|?Nw_Ym zEaPOFQej(~>P8!PqXpe)qi(dY8||zcO)EV8-x=Qx_t0zaHUUW7Z`?oL!^Yohy8_Bb y0C=I(G_;cyLah;cVdxAwqiwFP!00000|CLn1Zqq;zef2uFwkHghY{| zs@m9Ndlkoyyc0^lgn!~U5Pty}?K(*djZ`y-wLSaZn>S;7Lm))=LzK}>vWf)!k5p+@ z7661m?^v>2XCC|r5xgUov1IAOJ(f(s*4Goc{XInYysYy0Bvu^UsCWl^!@a>Uz>e8a zMS7M?PReSA2UMi0plmTYoGhkO#XWeZl}eQ@4d78-*WUnZaK+N$U0wk9y_^wtj_l*^ zX_BZ&$^4d!hmDJ82ZQ0O-6)O*(f$jg?4Wb5V$R?>@ZOM0GluWLdrL)Xvc^)9Xkufj zuIpchRM++I5Z+N&6%viY>c3&N^{B%#Xj04G;kV*L+aTrdVP;LtJrPbl{uGcO4M|uDS0S zwa{K|bYLOy9Y5$G@Ed~fm<_-W48$xpe48~m3tB=O{u=aGEVCA3UJ|zLadA?xVnsS! O;{E`AjMSfg1poj*9T$lJ diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_03.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_03.schem deleted file mode 100644 index 9dad718b4529c2691737d788f21caba384e3a727..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 646 zcmV;10(t!(iwFP!00000|CN=`Zqq;z#y{I}>};B*5)Z%$E~J&xP^wnSrD{bFKr10p zqzF|Tdu^}c*pYWa>638fz=Lq+J$MW-TF3dL)&+Miv!0#rH=iwg%>ahhdoL1GPSUgB zd=QzEX#&8q&_3nFSCyS#4DMS&+~>p_Xq<8qfbsQ=fBVkRx=ORyKl3HIYfAHQZ?M-N zxajH)nS_@yry$Ly*bzx66A{hF$K&~ABB_n$MJ7X8j0Vul^ZX}J7`!Z1irv?_XuRW7 zf$m9hxqA@=(vv*iaPef};>BTqaJ1|BUfkHvM$C)YP= z>W}yrsfM?o02mgY-RX+BzQ^!-nkqFSIU7(LJC(OUIr`}Jtd!c_$=5Q!&7f~!)Sq!~pERZpdJiXK4rZiJ#W zy=~#+zv&CreSAr)=2Q9VZ#@c6+Hno~%6-Se`D35-17wZcu7?}c#C72m$1MkpA6SK9 z++EZR!z{BtXde@eQdhlOQY*sNd*(uC>tt6@H=?;nCg1=Kx41tWh6@|E=`1I$bgkvW zVMVgrTg(9i4A%r)!__s1b;_}eCfLg{`nxTYZq*QXyQ_;fx?l@E!1Q&oa5lYe>I=V` gTr@6=nzfD=I>pc9EFno!sL~Do0*^UQKa2+e06DrqSpWb4 diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_04.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_04.schem deleted file mode 100644 index 441452af6de9047b1e944af2710f334eba3577f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 746 zcmVvy}>|2LV&ifleU-bWpomyA6h3~s#fd6wpN<7 zPC`{>5+_)M1jU!J?vLz0?SJfFOk%PLffic4?!M&s_}+6*Y#d_)*iiZG_1Taj_XwC@ zSSolD0{|i5oKmDY)A((J^^qatrwy&pV>8Ty)sZ}=a;cRPG zH*vjY^X?wndS=^tW*q|O8ftsXY7hL@t^uH`XRf7Z&eb#5)-+cmaHV~?P{MyXTlFVz z`=k1peHj{hE4!z+vRis9yRW&j+j?>bnsPf}s+)@fkQcByj8%}Mr$k{zZwdWR80yws zj{D;WV>xkVhCtDPl7i(tqqMO&Bg`dM=~*TDl=TEEk}ze0wN`JXiwP#!IcrG5DH%lF zoJzxzR7qafs%_=2v+`n?nW_R9t^#hgrW#aTNhnfr^#(WqTQy}SvuS3|D)RMagc6aV z%4ImZ<=aXEAc*OxW4PUvdGnJU0pe0OI^w*#H0l diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_05.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_05.schem deleted file mode 100644 index f545cbda97334f694fd464c39157edb7257a80ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 784 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 diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_08.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_08.schem deleted file mode 100644 index 4c24f42e32afbc61f5d0d2d0ac81cb569e62525f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1118 zcmV-k1flyMiwFP!00000|HW8KZ`(!?o)u-1BK;6)k@2mk_EIN+<0M7{=n}+ei#}qY zKwUs6j5WEE7N$snT-D97m;QzRr~HasdhfXx6|=jfo|b95ZWo5MJ3I4z-^?r*lpO#E zT0cZXHll(@23S9`yx>_10EPkXTPkQ}Y5(bf{T&lDrh+!1`IZU>K>j$5fB(yY)_ImE z@oCJ3Jwq{1o*h3sI<{dCH+VWYOQ`5)`3Tx99q^P5Czr1;Cs$WoxM04^`G8Am0QhY> zoqhn20?VkvS&g%y`8^#m*n1?X>tTUQ0A(#O>Bc8G- zr+x97@*G_a_+(}q(}Z1Q^y=a|%iPFwJr? z)bk>nh+$Xc6Lxq_1x>q{=xsun*2ZbS@X~3sT z=?p0z-@(NwD~g`rPvCnB(RwfoXC;CypmB;GxbsAIsFYtR!$e_=&0 z=7peX#JVSkV|<ara;{rSR6AlW4aMQksuEp2F3R%fY6)&{J5(9aEfNe|9!ZD`Ci%vFLAVrqX2noC*TRZ<0{A}wuj zXAaw1PXjEe0sVGJi&VASC3_`I6SO3wia zb#@2D1gqUq*>3p&9Z70=h`-tvoalE5_4#4&+L1o$0j;_eu5kcru8xWKwEgpnEdLxij0v`GV zei-5ceLU$NU?GDxx}%?s#5k3HNaF>5C~;Lsd>OA6fwnG=6>VzBFi*_``5`~0g20#C z3Obasd~^;p8_pmoi33^va;(+x&>_#88ZOMStBZUw<18!me=zhd38tf78f<9r4Y88-A2^3fi;)ok0Grws-v9sr diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_09.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_09.schem deleted file mode 100644 index 32dd609af89db5edf62203b12ca3de7a47192c58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1278 zcmVofRc&C7G0LI;8c~r}m{v0LOKV2GC0oqb+)g zfdX{_p)l6eQX))|0=cRhpubV{pZXvDhXO^RVs|w|HeUGQ=qg~si{c4%csTMyKAzn^zn$IP`Jx5-tJoj;GBp7F zwpc9w0FVTiQAzWD9EIw4Y{H@aLYj|Xd7kh3EXc926pK$UPR~An><-;i_vtwzSD<$7 z55*YF3g};Qe>4`*u7Lgx4@Y>4lsqrt0x2yPi@z{vu~_`w1pP%2xpzqu>Q^k_LU0X? zi63$|W}bM?{1{y|_$WJ@vVh-2?CxeBMZ*F6#ZUULe*yra!LtosNx*Ig;`NmGZzCb1 zNpH>skArA5;={hjf`nsmMT6@N!5$A?KHw}7eG$)iFN|U_R#Ckqnu)PGp07jp<~$J@ zx~74jp~pP&!y7l4O{RlkG!N4dnMG|?8VRkkc=C8QU83*FSAC;=_hgmMSJU9*wd#!- zj@RgB5+%t%_*039zPbiyJKH3p>E<5QHTYy-mX$LtU4ku*^&VF8#(yrkw@g@Sq?#K1 zxO-r|U8A%QzV-Ixc)JpcbSe%s_+o>cfU}=@G8p=aV4=(V=e;SuiW0%Yv~e3J#VCh% zQ4Y=A`)=6z6wP-lZ||u_Lgcodk07CdLZ_}A3kUo|fZhmqR-% zhvvL-qf5QtPRfxzEl0Ksb@?@m0knRFEsJb1Nb!mglD{OR;JXcE&3G@XQm}SwC|qs} z`=FLaNM)ObYX0g&UTcLf_6YtTD_`o`uEByTyzix#bE)MI>Wa0#`Qa!)zAHg`ena=W zN*C>ZeDL}T$m8n3h%t~|UKy_){3)YLS%0I8+Adi!8LxT`)Vng(l& zcA3baiZF#^imuYvDrz_+Ivx{h;@6lw%Up$4YHL}u$fF9Oc1np>8~ z5J;)usl7(o$!pM;{3I>B#RC%@Fethr7yKoEo}gtQy427`2{MnKwMv(Y^`(Xr0PRTb zbf|R50g$dO-HB>5P!n%kU_xgJA~o146=ik{Zw4J~$!pb=)kdB=vP=LrSO$RIwU=kd zV0R^JOKPwj=-Q~wf@4f$r9y$q4OME@fDoCI>c_ICU5rr;#uQ2ek^UBjnzBQyow?6m802vNSu@1TM8!*IuT(%wl!3Q;U& zgR=w<;9a&-r+2=rypbuA{8ohUbVXIwdgWB8Q9(lK)c`~O!|yT_zVLa{#3`mw`DzF! o+x~WHfp!%o>3=iv-O<1g>8AQZPod)Ph?QCY1+KcDWStcN0KB+%LjV8( diff --git a/src/main/resources/schematics/trees/pale_oak/PaleOakTree_10.schem b/src/main/resources/schematics/trees/pale_oak/PaleOakTree_10.schem deleted file mode 100644 index 8564f5a7fbfc0fb9f236c04547209322d1051ce4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1462 zcmV;n1xflJiwFP!00000|J7LAZrer>ofRcn7jC>EO(uNmQ~OdUfa6@y0DcJ)v_&ro zP@pMb6vkRwN`xiSAXiQECH;o}O8=uM&@X78RLt)3CfO3@Bq*|mp&jn*%sFR(a3_mA4Qr)Nelp%1;giD^5O~ma{#^Xgydr`c^lf#xfB58w-@5CzX#BN zousqzi*Y1-Wt8rd`^Wc>j(f0=3sF3IHRCc&(m5~@Poh{%7w1pU7Z(?i976YH8ciaV z8UTJ@E|-4*D1iB{Y_Pts2kr0qT)_S_W!`%khS4bEvkD4Vqxk&E(eW4eM&r@Z=)oh5 z+=9;QXe_6&--7N}BAQGkuoiT`74bw)fgz+Y%!EWp%jNPP+_YRS{~bX0*(@1dWCPfH z#b-iFVF8^-u^6R1luvn-qN@d;l+P~tOq?bB;_OY5j8FNmQFik7X8<4;M5R|MvM4?q z%@*^^({b`9J_-3O6NiFl(oEwdl~Xg%l0`9JT$;%nE=4*^CKEBvpNLGJ$aEnvU+YHs zaQ9B>LY!hg>z(;Y%-uL&yKz2=ac?STnCI(Q@c4=M+Nwl&gANSy@cR~gc}4h{;J=9MbR1=p$0Koae0YgxbtXk@ zq`$H)+1A&kQns}tYa7)X(A{QQJZz>#0DJ0d76;gen+>wTs@XUtxlLN?1HXr9o3z)x3%j!+?EgEu zHLFwb%kBDn)Ht5&Z{pR{3Ms*D#FS&^n(`e;X|#;H#TDcTte zT(wpk1#I{j2ELQ|u#r=h#Y#m9sgtU)2f9Pn52}sblrTj~l_u?wTdN(sffU_=34J#S z?54rj9jj+Pg*H}DJK5{??T|W9qm2nl*IC;;@ZF_7=tjzPLr|O45O+wAR)CZGnPhb> z6F`e%#4yjaWi~U_?DWI&E!`{3^_149q*d{t{fb^{wZgYYR&SZx1x0i+Cwq|^7tLm; zL%>?&;1-kh^0>8dM2Aw6yDi z%3zx#Sij8birPb01{5qf02{@md2Y?S-E{5D0vVX{fK^0lJzN)PK+(oE zWUnwFVGmGvVtQX;4;WIVMm{WyqF@6s3V`L)Kyfhua2$g{zdVkIHtuy@0LLR}d%$#i z{VI+1K1|6EFl>H0Op3*9smFU diff --git a/src/main/resources/schematics/trees/spruce/SpruceTree_01.schem b/src/main/resources/schematics/trees/spruce/SpruceTree_01.schem deleted file mode 100644 index 41516a287c8a14cd797143cae4fdbd4844f6a136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 603 zcmb2|=3oGW|8H-c&AaR%()Mujjh~sTF0G5$nBX658nPllb9RS^PD7Z}qV82r!qszn z9^IH~tGVHs*ber*me~*NA~te9=F(U(X)bH1bEUjyk&*rU{;3h_p6*MOv)4Cu&eP0h z@{d|yI&HRzpy2t*UzeX+6|WqWIxqsS(kZUV77KTZxfb=e#rS?2CVvWWCvC!f*WeSeMY9 z#XBQ(?8M#6yXNoybna?x?#K95GpC(Bc=YtUM3I>XR{YqW_3mHf+2({VDqQlmWe~!K-?sLhUvQ6DsB7T5+`y@;Uqo+QX;HSsvg2~XE3Yisl@Zaa zZEw!ld8x_A^_#;?AH8)??mdmDn|7A@ubSU+^;dIG{(kR&w`-RA;nm(-tgip`%qUtt z$>DqT?ewEtpMm`=`HMdkl zrrgk&%UUq`$3Ol*yHDNT{QCaVzdK+27ybSB6JK0m9fNkyKmI#$qW|xQe*eEeg1h1B up})L;_W$_H2Z?VgksDTBOGy@3ez0UlL~`#rYx`sSnSV_2c>mxV0|NkzP8SaV diff --git a/src/main/resources/schematics/trees/spruce/SpruceTree_02.schem b/src/main/resources/schematics/trees/spruce/SpruceTree_02.schem deleted file mode 100644 index 6b107a9c50d5a273875eb2cc3226ee1a0e1cbbd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 666 zcmb2|=3oGW|8H-e_PgyM(e}{3bM6cUuGQ;yz1Y{VV9O$f)aI@$uF(^;4l*swdek#H zP)7RZWVvg5TjPS(FS>p))VxH-Z>_+_(3MK9-zP4zDE{#$dYh@=C9%xYcXk&na6dI` zgW_7|xlh%6U6x#&@N@d9Ri~#deY}k&e@m$C^;ezGt+sl+eLemC*V^etGtvt4x2NY6 zO#PHlT$)^3o4fRN+S)a1**8V-7 zuk<6o?|Hr_g|V~NZ+XeTrsU4E?CrA`ZC#<&Rg~MkD#q2m+w*93^epQ_DZW_GwY8$z z*`>~6ZtJ!f1W&vhrZ)NHM4Me%r~Nk-uDE|Z^S?)}QRVZ`8>IuYPaNE;IML6kc1dWS z^~A2?y>ph#l%13R{N<0L-;1rqXL7%Jeb2SKY;um-HP?#2`iHxJny31k^`F<9a`!d+ z@~iA~<-hvmO}*`#p8Ru(1^3RYoqH9xo%pmW#dhtqr{O27!gNzCaBKFhp(+Y$ahD>pYsJ@CKT z`rQ9*+5NU<$L9Ak8Z3_Z-}ryJeQD!YuWR+1*ZynX)zzG-zx1_z!_1?<`0vCzdY0$@ zYS{FgoniCpFW1fIy>I;8{abp+{!{m%*=}7`4t4)o72+HnY^pC?Jq|gx9?@WTI#W`$OjRcK2tu-Yq)s{p8lkvN*nI)!G$y_HX^_$~Rx*SpIE( zZnpEv(n^tEX$t+X9=&_L!QSA~*7o&pOe(YELIqN8Jqf+pRrj>{)9=0=y8|Eb=6bGm z7R^j9ah~S3%|t)6@n&G6*5ve04_4_~`yQY7PeAo=@>k6>yXO1nxU5y=U+#2n(vkgQ z!TBe4ubkmZ+fD-?c^U-LU)Kj)hhXclJ#B%Kw0G_1Rqv4916lu|J!peD+Y? z_RveQ**jfd-aYiLrf&HL`2)Jg{_)?5J9;}iI3`n^^~T@b3eCnh|93NceXHO8gV`go zkRh%6ul$VoqqjFtd2>JY{(o1mUETGy3m%-fmr04t4)o72+HnY^pCEl{(o)g%-uB6`dzZAGbSn9Uj z`J(LN(&WW;-1}pT zR(coTZz)sw{_2ta^=0-OE|s?H-^u)RanCA^Q(MliE)xBFLj37>*^k@ZJq=1!w|=|g z5xv?rQeyX|rV!UM@0lTf^H1LWyz!03T$W#V3n#U(ziK|YOP)X0 zy=#40F>3Gin~oVqt78=Jwx4$YnLRU7_V)fc7U!?7n3TOZYF*mY82(jHKdpWewSL;_ zP}S>cTVviCSM8cRgQ^K_h)z5G0ANoy0P+?{qPlVN)2KmIeT$^yTiGp!X5Hi^BlU;FF- zMO;!rpT0V9on|gr`QrZp9_>GW-~7r?tk!#d|I_;Y|7WMh2QZw``(sfV+1;nh51c)E|1%_=QAn=qwv4ocQtH~K6Wn)&VaXR>w(L@yde_1v zHfR9+Joy5@SE|H((+K0G{?C(u&&qEtV$`1|)PxnI5Mj-O8k z3&(gF)eD%FM$1iE^2w=n?Y3#VENe0^Em@lx>Nr7TZdj_;uZezM?_*cylq>H~_S*D) z&$>%dQ&ShFdwovUzYrZKar%SLH~m|a9#=eAUEcWW_p5-)&UNf@=7FdGYFvAJu~3Nj z%*H=<-C$+C&+#dzKAEjAi>?<9|U_+UkFwe%7C5FFXC%y#J@4ZwvU~6_E(4reW z!f$7qeC>Vu_>1hcjH}nf_oQU%?Dkl5HfiH3*CbmlvD2DDuM0OuVhG=8T)q0yjl63N%b%sNmS=^XJ?S;kHa_OIp=k>5iB~Ki>aP~tIeN4aA zCzPeWzyIeS|CvpDUVq;6b?(pC3Jub|UwQAu>C`{?_K1HMBzclg8oZW?yz$PhE`9lW VXW3OpWVilj*)exv#oauomo diff --git a/src/main/resources/schematics/trees/spruce/SpruceTree_06.schem b/src/main/resources/schematics/trees/spruce/SpruceTree_06.schem deleted file mode 100644 index 0888fab3764081ee6cb97288b0e2287a1e677234..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmb2|=3oGW|8H-f%)9I$ap2?n!er}p5q@(V9{F`^bVn?S(b6hRm@H7RLZE1FVUlU@ zMB9Z0&qOO&@3m}yU}{mQX`FdOB=uO3qwmT8>Y;^cKc5v(?o@wlCg{23vySwNzaiq0 z3KzLUuew?aO;nh$_4i7@`e}3Or+WPHc)2CrD?IgTh}OiZ{qp--=KD;Uo@)9g_dA#D z|E{8M->yoBo3GD(Gb>K|x>>Siy0pmRf0q+iecp6y`sb`G`fgbU_x5hLzIR*u%d8gv zRfneB6glHxQBm>Zrrj*Xx-Y9H->loX#?J5i)H%0j+1#y|^xQRU$>IB(c78}V3*U5i zcjfzQvcfS@m+B(Fd~wj*u2Qq0>z{-7bs0O8!#9Fvht9k1b$gAE+x;B%-!nXRnCfh{ zogDPVL{@8Us6zTT->k@~&wj;4g&0{*e!lmR`Pu~sOQNZ9vNT4u9-!_m?^ zKlva0Kl+D#n)qjV3(H*pQjLp$Zh0KCC@JM%D%@V&p3z>YUSKNFX}wXqPUYM^Gv@o8 zyH+#Gw1575@LpBv>py#a7<44A{Xf9zB~v;v|8Gv%hjnaajrme%(i??RyV%Cd$0V?U4{?+8~?NG7<^|nD!DgL{QOtzlK1~lKKZwQ^{spV%he8i zW883H$3N!f(|`Yx@BID#`;oZ+cX?}=86qOyy?j0Q{=E0%41UJX%s1@0F`s#w`2Tu# lNOmBL+_1)~C@ni8t@^fd>C%62uo>ppSyYUAD0|2h^tS0~f diff --git a/src/main/resources/schematics/trees/spruce/SpruceTree_07.schem b/src/main/resources/schematics/trees/spruce/SpruceTree_07.schem deleted file mode 100644 index 4b1d884bbdbe3ba4d498583daec33844ec9b86ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmb2|=3oGW|8H;m`d@L7IPj4@>f2K7LnkMz-D725AX|FW)+3t)I#_J8iZ8zRVD%VJ1#Zu)HYE8aTu z#jVIGx4Zep<<6Zy`))?y=Hkz$GqtQ#?p~`>zfw5s?a3{#woO`>^}TFbS=H{{op~Oq zy548DDY1TDB_k(Sr+e?Lo}b&(gGUQZKi+St_F5M9tNz@&qlH4MiO*xq%InTQbKLS- zC(o`jtLgL(pBy#cyVHI@4ys%^DJ=T*rk_7fy;*i6Dn<18&P%RgYK!L|UGT_u!YMEH z-ZEdWt6w)P-V!#?=iSUJi*$Ov2F*yhH2>tWo7-yaU%S4lU9)TAt)Rqp(;sh`yn1Hd zte4+DKDT*0^SssTl{1+?+osRmay97e<73RFMrpT?$bEh?SGU6cglKYqb>d$R_5q@M|LCyB*KKG@5m;4j; z57R&A+bSQj@2XFc?5w*w_VaG)*Cp-@i>*C*J?hA+X`Ndm^upHqtZ6OU_$oEYR!dx4 zLpXb1%o?|u5&ox+?kYXXr1O<~&XlbG84_K8CcI8Mb1mc4t<77j_if!7|Cym}%IccA zU+q7h4`M7hYw`O3|0RqG3^8B%A3XcSK9`}PapAwli>`l}7kB?+cMEI%#m=@oD|fBu z-90+59Pi${v!P_gthD>L9DhC5m7MYSc3Q%x?fwBzrY)8|5E|Lf$GSf;zubgbB5BS4 z#@D9{nbxy!yIQ&N0DsP5+1LLUHJdiiy0PBgOUHqM$LnzAzwP(x7!1z*Iq8_r@F3{g z=k<~Gx80-P|F^#W|J-Z#<6ql1{9|W371+@C)&42>ofw<{%rVLJrT_15Sa-jF-v5of z`s?I%{67@@mzPMJ^S@Cs&HgX@x98ta&H4X!H^>bN*MHslSfhWs`~h3BtUSY=m?`!K ryJG&&OGPCffK5Vd z57mU+#}0@?UFt$lf`y#Ccj zd6pNG;lVJgqIXd?AKtxxckkZrk?pm6Z!eN9w?Z}@KbfS}C@)TB+f2vV)Ev)_9v#h2 zPO@r4!pB8sm$?+|zwqqr?05IuZsJX^GP&F@lJ@uMsgbSy`oFu6N26?zrIQQU-1&Dl zpAL?`8Vw(OX@kp}tUt?!)vE_R0pKrfTWzOd7mo9kQ^WN$pi@mkCvDX7RxcuHs`m-qyhgn&r zb}Rk8ofmE=C@V9q=A`Umg-v2TY!<82B-Z_Av7#oiK5G^$ZW8PBX0Z~_PIKXNy|~Xb z`(QVkB>JFPqTL1d?uGZjUXw(>XqM<^^GaE`3b(FU;*Ab5+tR5|38~1RvKm_T&-~_= zwAQrdprv)M19%evupDi@+10<=9(d~4TmJx7man{h^}kO211l@EQxZ)p)cgDDAHenX zU+dq~9l#3nm3N>vN3mZ2$)f21_#gR(_j>(v{s*ozPyKHA>L0*|;~7Cmk#>DAkvNXD z-f+RPwF{7umY%~50=PO)eR)p*zWQhQ58Pla#{a<5=)>QR zZ~Pp^u_RrkSsTA6=i~qR69l_LPn$p36-1qXbdw}f((6dqHf|~b^hV&8^G&Zk19;E) z-yR=A{Sf7uAIz_P088f^|MPSpaC2=@|G?@}2l(AYV*U189LDoo-35R^yTRwi|JnrK zg;y>Q*vxG2t~Q7F&R+mrnYX_5`+uSD>jMC;F~+$@%()+-{k`UtIT))^&B6(;|~C< z%Xfc`@i}mNasM5-z4(Sc000000000000000000000000000000000000000000000 q000000000000000t|pe={v;oq989aM%1oL3v;1#-43r>n`Tzi>5H4u| diff --git a/src/main/resources/schematics/trees/spruce/SpruceTree_09.schem b/src/main/resources/schematics/trees/spruce/SpruceTree_09.schem deleted file mode 100644 index 66cc34071dde64b651da00ffec498dbde8e11a2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1122 zcmV-o1fBaIiwFP!00000|LxsTPuo@$!13cG#38W*PkY*=y$`CguI*Nd7c`_jY-^=y z?E^xSNp8R*BvtIJ+w-(X)o;FFjgj%b6jYFr2n6&2)@_HHwnkixwdIr zq%Eu8^^V+0R_49B#LwNN$j2iQsn@0RWmabYEm_}hOY0j~W_`c3Ar0T?M55n*`SbT* zeUumD$*{lM&&$^9DBZ{R9^Biyzj@%-+PuHjlC{twA00j$X60Z!IgxcYI?PAzXnOGE zV0wICPCu=*iA2**426->fh8ITj z!T(3|N$=o`LI2_BKDg9m$MT$PbtM*cUhm;YktKY({SbbSvcLN|`PYI79D-MJBxZd{Az??A%1 zeEdY&Kw^w94PW!0-@h9Ha3#j!KjgpK!Kx2p(b6BlU1sGsC-{KHwPM!)Mga03ct=8U z+ty|XQnh?;r%AUfN{1xW|7rmq>i@YlcBj)>)9d@$48{B@3?D!n-s}a?CO`lv)Z_|c z_4wa4>;G8v{FjvaA6Qgezz-}f>)^a@oc^~X0Jt)%I!@HnjJ*YY)NYyTC2d^E0;Y@1;BdmKXQ;1MjW4W}N@y_z&C~>-<@wSJ>A1yHNit z9m4I!`sKrkars}$3;=Lztob=(|KK~k#lZl$Qxg610I6_?0f4(oxjAo!^aqxgb%8DU z4J@zg{P{ru%gx$909am}ode!qna-+s
1h3>#;B?EmFBF;2E=G!i)n=0>*RiWvYK^ zK69sQT`jBp$>SdkV|E_2UfU71vs`5Bg@5&&FAMC?&Ed41Tb;h5^U9K6Mj5NutzUd# z@~3%SlULjq7Cb+Br~1Bc`?u=ITwC%fCUbvg6nBupTrZXH|Lz{&&F?$qZ{3~DJagq( z%l@L|YhQ?gZGI@iJ| zH>oo%*#vGW`-rs(3qQ+YV>Q4pO<}h(5+Wrnw2L9 zRbGFuv39EZ=h(?n0>2mD`?5HqExhRW9$m{m_QtBKFSqmkF%Jx?ug-9LVsleYe$k)n zT9z4Zo!_b(SFP8RTYh~~#?{NEviw&bb^cU7q88+B`g-Ag&k!l?`Ks5VwnfBEi{RgM zR4b*k)INGm(zFQan@Lk`Zc2BHY6(Ad_}MAP8}sIC@?AMs&tJ^N7FlxLJ1Q2Pd$#1@ z=}4j2^V2e(nhVLw_8a{zJu+Wttyg8lm3=4FXY%dzRA)#?T2Y@+7Wn_bn??T`Z@T_v zzO4QJ#j<~mt50wKCbnnMlpN-+pNn)W4Cim=pRngKOCO8uWX3<Wxo2l-}_(xWNcWy`qhi{|NJqr>!f#-HpdJ7{#Co<|90LFZ{G8#?)bNz ze?vL*425_94{Uu}vn_9b{qA}HcRsU@H(;2d@SNM>`mcZb&zK(UFIva1_rFU1KxyjF y_Y4fdeV^FV!c#ACLeegIWP;Nwqy1N3vb*25m3`@E`k diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_01.schem b/src/main/resources/schematics/trees/warped/WarpedTree_01.schem deleted file mode 100644 index 3ea16983bad9bb73d2b5a8a78071393ef13de3b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmV-|0e$`-iwFP!00000|AkRaOCv!LebbXnk`XZ=d)k9PK$M7LMDQX8Jh-bM=wT75 z%=F9@nGflyp#BiOdf21?#2@7>-BF|FuyyF_SFc{xqdNpb^vZz01nv@hE2L&Zh*!&F#&tK^H5Q;4(eS zNs`JG=<71&QfJfg{&+f>uq+`sHJq|r4WQpN%?scRzN_kRQP)N5Hx(KyhYoI>CJ9GO z`6m~vGZ*W_!C+@&xVuL?+q-tMLi>_q$q*<6$C}ej&{haebeT$qwzZTbwH9lsX_{9n zvN=R>m{-xH4$(QIT#INQ%LSJ@GL*x26%0G^MZ z3thKl5lM3Q9pic}l@v g-)VPz;8}LChh_Izqf%H{*WsJwAB>EC;d}xB03N=)8UO$Q diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_02.schem b/src/main/resources/schematics/trees/warped/WarpedTree_02.schem deleted file mode 100644 index 6664ada5afa7e16d6e141bcbacf7062a69c1da19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 404 zcmV;F0c-vriwFP!00000|D93GN*qxTJ>7SlM~f&(R)ORLf-*sIL~t`GWD%nfa27$N zru%kxG5wI+CF&xdkuS*K zrW`XB&s=P`EYZFc+M=wP8ljbZJUw+nA=1^xTO&cxFlC}1a zBUx)7BXq|_9nYEwy$dR|h*sDrxze$rRE~&^TUB_!!0yQ0=;S(oxXDR>qkC?SAFcod z3U3x$O>XMCEW8yURCv3ha7RY2r|!Hz(esZWAedk&f&j0K|0V>lm7ceud$s@fQ>eC6 y!3%T_tH5WaT8^*VaoFz9+0S@Od3mymFUJ5I{@bUOu&{2T`QR@hx>svG0{{Rh*TQ1} diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_03.schem b/src/main/resources/schematics/trees/warped/WarpedTree_03.schem deleted file mode 100644 index 07a3e03d3448e73b6893b45188d1228d55b29959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 505 zcmV%K_es4JC`om#d@OE;x9`Zk(7ckb0WGh!BU00+%aZpcH@ z(iNy*u}WnU0}ujRXQb)Z%+9TY#s}8qktQ4V8EFQrhp(sKCJvg{iHf{)PwK|JORc-N zzxTG&K+7a#99%}E{X`A1!*L*E4oCgt{%A0edJDA+B?Fni8bCYCvhP4H@Ubd&R_hwD zKhluVI?2P`3*VQnq^O|c`BcT5Zl`mw+dVv@gZ)F(xq|Ao^mK?;1+{l9gHYo|1-17a z2WHkt^8J*xk(6cG4^w2G4r(V+;to;=^-GFaYZllZ%9ve6zCI?Y%u<2Z^SLppd-m?a z$$dzCL!V{Zo7@7h1g=(A`5e1^!<6ctPf^P4I8i#BjnibL!=6?nZjVW`ijp8;ZHNLIgbff`5!< zEyY-OD!(_l{%qWdEwgxJo8V8_<|3bC3Gt-CD%OIc0~1}Puv$F9Z`g^F`<0?xoCI1xLeF-a3_6nBAW5c6#^@6DUv|NQaNF5sf`-iv6& zmGEq|&Piq>O#s+7*7{uWyUym7i}qVm+~ao$C%#baxr%eJ zv%B-M+s2085J`9za}}g=giT69kx(=q91X_9p-^jZPNfJ%@n`_A@;v_r6b5IfGPioJ z4fc+Y2ph*mxqTW0!V^4RaPj!3i{}U3?%wvn{vqGn-Pg(uG%tj&B0Orqc}*gW6t)_0 z-cSjrEa8$df!!M_fofYUA0wH{tSz z{OjoUCgOC-KZ>k(dj-II9t5- zeXY%I?Vwe>og8@j2@i}_Dy`-FhDtytq2-u>>aZH6`k{=hCRRBS`+o|Em21X+&4XEW zM>PL%WUPE4#+v1hwb)>KWT9*>R?~dS0Pc!FutTz)7Nn5|p{qjUT0sEHri@0091O B87}|; diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_05.schem b/src/main/resources/schematics/trees/warped/WarpedTree_05.schem deleted file mode 100644 index 1f6e7c9e0955e805ac6cc34c1301bf74c5dc18d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 632 zcmV-;0*Cz{iwFP!00000|LvLGYTGar$N!EMCvhq57`xdRdw{hiqoo;z+>{0e`)FZX z3oD^0iEOKJ>|!fqeTZG|3HCO7qU}n#_uYUE zsNh{2l~b0aJdOa^Hr9@)pi|3c+eP&)6SPMKB_KysFu?lyt@Y*7MeQt3!d|P#MRgX% zY3%Ioyr@^PkvVwepM+F+aWcRri+mojVAwh64Eud9*5Di`+~+DafY(yW?@~(XqW1e% z>gxBZAn)jaVdGG3w~sxKcR3B`s(3iA;(4Q9-`j5NzodJ+`v6cv`Hc5OfCnWwuNe0O zfyX5{Z&>7K)~qDYOPR<@l2ZQ2E=ehWx^ND|xZ6)%R8DBfgkUz-2RvfkgnHtD@+3QI zZ2*>ykJ>4ZMO}8zXeyc>4O6xi#fbogvUped3e z+Zs{9k}&pt)|)s-EEUR^#V*-6T@+iz&_hom-5lG?mhJI;Jkf%WXd*5;nKl5(AJWga zrb@X>`m&8jv*2lv#PJ|huMlu-e7I%bb9omjI!#+aMNK{ho_@k|v8=`Wl}FQKwFRmQNz0WhrJaE SE;viwiQ^ZjEI*gAAOHaOX))FS diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_06.schem b/src/main/resources/schematics/trees/warped/WarpedTree_06.schem deleted file mode 100644 index a5cd9605790fb0668231c48a79e5626c0d056b12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 717 zcmV;;0y6y{iwFP!00000|Ls~qPt-sd{id_i?UoQoh>3{@<5?gW2oMr31dIm-5)}?e z$gn$YCv;mfoelCs{006SuU`Bo9@k9ELg|dqt-3*-ciHyMeD8bTYdfd)0etAZ?MAFm zCGTQrA25;eGywpNVQQO7dd{vV3$+!) z(;M!BigVUGi7wA0%8uwqo^4(p0|3JCUfUJ0q|1&N&E%#O12&(eLPo`9mJVdJ8PGUm z^A}Vy5vO6udLxEqvba>*4u*r8+C_ObhB&^*cu_N+b14S7b_>H3P5Xihd2*_92LN)< zXyxuf-PuO%T@3FUlpq+oR6yVu6c;G;& zIcG5r>V@jqoPQapM$ou5DL4OD&@~u~@ehh-W&d3Gq;aJo2?x2qM+vu#@}z2){P{5( zL2IWuS8&=ojcPharG=)$5dcKA`hu-$dRiOfND~fP@1|T;S%Q@NGD|#@79r5lB}-4_ zYOU0P7@xHsm4}prTNd($x2T*ZNT-OWH<|Pst-JE7obq0oNZqTA8j1AcGE-cZC{0A| z(V`lakSe3cl{>3)lRhVE!Qjf^Js+{=M$;>MM>|7Q#AK?fR5Er~!v_viTBh(#XVKWo zhOGXgs4OfjEG#T6EG#T6EG#T6EG#U3iVjTYe@xm*B$u3J{?)<{yG8G-=p_IEwC7sy diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_07.schem b/src/main/resources/schematics/trees/warped/WarpedTree_07.schem deleted file mode 100644 index 28b5ad765de4ce8e52869afa7b695c522ad9017f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 789 zcmV+w1M2)AiwFP!00000|Ls~qPunmQes0=0H9{bvX@?!Q`v5V4=!C=t!L-9Rkfy-_ zA!J%7v6?0*amDx{`vuef%%uIE9XmN)+Pd~=nrZ_kf0sJX?|tvR&o5S0l>iWEer$)V zM+I*?fKOSP@gxQS$AP6oD(J|vdO^T@&jjsILF-UIq=EsUFJIsM_(q_4mZVYVsKbRf zW^r4a+ncYpJXnz%JPuAGD*Pnv!77Ub9<#82wRhF;cDYyr_c-MN&r<_HJQxhV1LOr3 zQ5n(WJg9%5Jq9ZW`Qyg1@AEdN(L@z%!zy03wzhUQTD!05&i1Zku0i9BcSHzlHE`cD z9)tp(*1&zo;y_wuNxq*kAxjzz20vwx{3PHWL`l1w5x^%jVnQ$nmU}#A?UedrkMdNy zI`DjKyP@ed>s*Gn*CA!+^fS-)Z!Z7$$w=Zt1zU!;Au9w(^?hs!MKi*Vnk zQO4G9s9-M zb4%_z@Ju7Wp{cmMk`n^}ct-j^v{o*?H$%57hf*AP(}5>r?kr4`q!;BcjeJRQej!kR z7YRIYAV6r^q*p=$;Wt*i?JAxE>^Qz`!uOtY%rCglPx3e-#Lg-Q&x z75Wa8zG@X~L-%owfi661;EHj`5ULqa>12wht}=f7LvB(_*TdMP>gF{8<5@!M%<}81 z;)#og$qB~BGeT9Trs{N62iAMXL^0)Sj0n~PRTI*vW>35%(WB7N;vqE|R;ECP8H9GV zBqmV}2mY6#%beTrw9txM-mNB(`F%HA#yb-%qt$O}jL2w5)}#tOu3<6_LDN~)ZnUKT z2!;}h*#Wgl#gm}v^5uS+QJQd{b{^#zQDd1VN3n-75Ith@*i0|YJ=_!}Q0Djz{meNe z25(&tV;b#RQ^&%r^aF(PM$-&Nofb@7@<10%V_{)oVPRomVPRomVPR1TO<2zVjdK(W TE;!4IUtE3xIR*Cb-6Q}2`)+g- diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_08.schem b/src/main/resources/schematics/trees/warped/WarpedTree_08.schem deleted file mode 100644 index f4d76f49ae47380cfb837a77c0430840083c5a87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 841 zcmV-P1GfAhiwFP!00000|LvR2PTN2b$H#GS){YQJ6(AXS9} zLTHn$?G=uV?2S^6RS$iFK0)8657ZuAd7Y5NyH3`$RRio#0-4#F*~$F(!#UspIADM1 z_@sv!?U>LwBXL5b5CBXQnn#%7JIm^&1Lz%LxQiLCL;VOd0$_am{`#xufPEgtLHD>z z85(D?_P2JnUTz~;$p&cXod%e>QQU)75_&Wwe!qRt?q6S1)&%P$rXJ-}1Hk7rO@E|m znmS;Qq7p}pLr{N@djwVv`JbC7u1h-<2NP91%d2>?zrDS?xxe=c@9yjY0MuaVoOYQH zYc;Um5bF61)@xwBC83vDvyxmlAuKB?P1B!QP@1N{9Iy_9sB@h-&^W~bVT_ot+@m4s z#MosAn8ul_3D3v20gi7-_sYM$@iDo;pJ>v$y#xTmgpY!kOTrGhAUI(y7Y7O12&0(! z!{;RGGrz^+KG_&xM&cmyJklKw4oJfISeCnH!r6UtyLqbF$<2EvXEO@tk6H5ioUX7e zNuHZOCiCldN4b~g6h4t#xhd`_xm7_<)ymnh;|&wmh1>%iv#WNt8UX;Aur~Hc{5Xnw z0bkbuwt7n|%jHy93zhY1LW3VR7+~uQEDDCiWGFgCsmD^08h@DdNrHwyN=VwN99y13-R;TBiIjzt zN@N?NC(5E7$vY$81B( z$z3S>=JCknMAEh)Bn*r+6087*;45|K)}nB^ywgUmMloFWY9l!s(2RE=VY`=~=~Ykr zMS*ujP>ydUL5_kLBQ?4Eq|N55VahezfVs%UnZ$~uMwxJ2o}$&h8<})1+wtP3eCP=6 z%M}*I6n9)Mq+p%+T{=mbZg##x3XTS!+@4-Fyc}ssQ;xG{d)SoRN74^pj8yKBV%PHH zTy~<1tA;z5l=v9BkrA;!vanI^PZU?C!t+&UhRS!Ub@FeXaYV z#g(kvP&*TGBBKxhY#SC1x#EM!@}&#TXQ6n5D_(~3Ay)#xdiZg?`pX4&9>qc9s3Db; zs#JH^x7If|99Yr=GHjj(TzOI4f@KjlWhngi_1<;+=0>UosGh{KDb3aZ@X+aWegYVS z{Z)yZU*|yi3vUTnIxx3uC!Qzkk_QTwL()GTFbs1OUs1v)n9CgmrNtc%t?_9wg#J7{$u(T_;g{ zaDB&>h=Zuv6pemFBx+y9ZK1_WQ;4g$T}azpYH{0!Z{x(37R(zr;Yr%%3C0z!UCemp zi78h_oL47gn* zo46SpEj&8@V#yzf6-@rPSrgNTOknOn*~*Mty^XRf7`lD0aM{IU9I-hpX2GY9nO}?} zvxbh|y`F}kL#J4BK2-En))oJ&O=7ESpJkQcJ}#e zw^8J*nr9d-8=8-toPLb+t25)sAhunmkun~#6Y#^}Pa<+oQ|7$FO95?|d~@kQJ|AP6 zYvMLpp<=N@X&qJ1qFcN=uvg+!lS>c;K@bE%5ClOG1VIo4K@h}P7%ZCqi8>0ER8l1U PzvTY~>#JwSV>SQ)4or>D diff --git a/src/main/resources/schematics/trees/warped/WarpedTree_10.schem b/src/main/resources/schematics/trees/warped/WarpedTree_10.schem deleted file mode 100644 index 239626ce9da45a565aa0382cf526698cf55c82fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 848 zcmV-W1F!raiwFP!00000|Lt2(OB+EHe>-cFxFQP0Q=$C;MX@Ma1TUiCp{+up9*W2$ znPdl(jqHwCKS)18PrdZgZ_y9bYszfaoiX#)tVUfe??)iX+xKtYZ|2R*Apt-Dv!7d` z=yN4oKGeU6IFW-00DK?jcDdrYWbrtF#z&!en=3v8GrL>~0Pp*c{onfm%pMKmUVE=C zRl|6xZLX}XyjyL+f_@;Q?qQFs&LHl?qKLXO65)Ast9gEUD%Bj+4r1ArX=niWIUElE z0Hg_CStWsM*MON%yf0v3C;hs7(CNsQof_Ve#m=5DdEF>Uq+%8 z^N!l$GS;d-yfLy19-oQ!NqBh{a`BaalgakwF#vczymGWIc&tvEI)qNshb5ztgz;d| z@1@D=5cQM@4zTtMo*6tu);Trn$H59WYXNgO8ik%cl~%v}^KNWQXWSrpjK z(_>ijdW{%IKBfwf1RZHS7(<8~t!S(Qxh*TRZy#Z7m}&${Gj@s7*68jY*F;P4pg>NN z?t$0EnoQ=@AmhED>6F49YdzhRiuW1ZrLzs$=zc)<`y&I3h@HAoFL71Tiw<3ecbB7M zKH9MOp6W?$O0#EVIYvZ1~yuuDw{qVet^+ic~FGc{brW z!>lPM+3qZP9hfzxnva%ZHs7Uh88t?hhcQziV|g1lKV7V{>pti*_hgRAlZ16n?Mh-Y zSJUp<7X8kqxYi8ZjZ)mv33u Date: Tue, 13 Jan 2026 08:02:23 +0100 Subject: [PATCH 47/64] cleanup Signed-off-by: Illyrius --- .../registries/MaterialRegistry.kt | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/main/kotlin/org/xodium/vanillaplus/registries/MaterialRegistry.kt diff --git a/src/main/kotlin/org/xodium/vanillaplus/registries/MaterialRegistry.kt b/src/main/kotlin/org/xodium/vanillaplus/registries/MaterialRegistry.kt deleted file mode 100644 index d8b5fa3c5..000000000 --- a/src/main/kotlin/org/xodium/vanillaplus/registries/MaterialRegistry.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.xodium.vanillaplus.registries - -import org.bukkit.Material - -/** Registry for materials. */ -internal object MaterialRegistry { - val SAPLING_LINKS: Map = - mapOf( - Material.ACACIA_SAPLING to "trees/acacia", - Material.BIRCH_SAPLING to "trees/birch", - Material.CHERRY_SAPLING to "trees/cherry", - Material.CRIMSON_FUNGUS to "trees/crimson", - Material.DARK_OAK_SAPLING to "trees/dark_oak", - Material.JUNGLE_SAPLING to "trees/jungle", - Material.MANGROVE_PROPAGULE to "trees/mangrove", - Material.OAK_SAPLING to "trees/oak", - Material.PALE_OAK_SAPLING to "trees/pale_oak", - Material.SPRUCE_SAPLING to "trees/spruce", - Material.WARPED_FUNGUS to "trees/warped", - ) -} From 4f2e67faddaa539267a29efe53f64540d3fcf618 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:59:56 +0100 Subject: [PATCH 48/64] feat/PaintingPicker (#357) * init Signed-off-by: Illyrius * refactor: update painting item creation to use ItemStack.of and fix display name handling Signed-off-by: Illyrius * refactor: update painting recipe to use DataComponentTypes for variant handling Signed-off-by: Illyrius --------- Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 2 ++ .../vanillaplus/recipes/PaintingRecipe.kt | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/main/kotlin/org/xodium/vanillaplus/recipes/PaintingRecipe.kt diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 29ed4ecc4..cad541676 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -7,6 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.data.ConfigData.Companion.load import org.xodium.vanillaplus.modules.* +import org.xodium.vanillaplus.recipes.PaintingRecipe import org.xodium.vanillaplus.recipes.RottenFleshRecipe import org.xodium.vanillaplus.recipes.WoodLogRecipe @@ -42,6 +43,7 @@ internal class VanillaPlus : JavaPlugin() { configData = ConfigData().load("config.json") listOf( + PaintingRecipe, RottenFleshRecipe, WoodLogRecipe, ).forEach { module -> module.register() } diff --git a/src/main/kotlin/org/xodium/vanillaplus/recipes/PaintingRecipe.kt b/src/main/kotlin/org/xodium/vanillaplus/recipes/PaintingRecipe.kt new file mode 100644 index 000000000..4937d4937 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/recipes/PaintingRecipe.kt @@ -0,0 +1,34 @@ +package org.xodium.vanillaplus.recipes + +import io.papermc.paper.datacomponent.DataComponentTypes +import io.papermc.paper.registry.RegistryAccess +import io.papermc.paper.registry.RegistryKey +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.StonecuttingRecipe +import org.xodium.vanillaplus.VanillaPlus.Companion.instance +import org.xodium.vanillaplus.interfaces.RecipeInterface + +/** Represents an object handling painting recipe implementation within the system. */ +internal object PaintingRecipe : RecipeInterface { + override val recipes = + buildSet { + val paintingRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.PAINTING_VARIANT) + + paintingRegistry.forEach { variant -> + val variantKey = paintingRegistry.getKey(variant) ?: return@forEach + + add( + StonecuttingRecipe( + NamespacedKey(instance, "painting_${variantKey.value().replace(':', '_')}"), + @Suppress("UnstableApiUsage") + ItemStack.of(Material.PAINTING).apply { + setData(DataComponentTypes.PAINTING_VARIANT, variant) + }, + Material.PAINTING, + ), + ) + } + } +} From 355f840fed022d7bb0a67aba399e436b60b7c551 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:50:30 +0100 Subject: [PATCH 49/64] fix/ReplantEnchantment/SeedUsage (#359) * init Signed-off-by: Illyrius * refactor: simplify seed usage logic in ReplantEnchantment Signed-off-by: Illyrius --------- Signed-off-by: Illyrius --- .../vanillaplus/enchantments/ReplantEnchantment.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt index 5f085c18f..2f59be023 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/ReplantEnchantment.kt @@ -33,16 +33,9 @@ internal object ReplantEnchantment : EnchantmentInterface { if (ageable.age < ageable.maximumAge) return if (!itemInHand.hasItemMeta() || !itemInHand.itemMeta.hasEnchant(get())) return - // TODO: take seed out of drop. since planting would require a seed being used. - instance.server.scheduler.runTaskLater( instance, - Runnable { - val blockType = block.type - - block.type = blockType - block.blockData = ageable.apply { age = 0 } - }, + Runnable { block.blockData = ageable.apply { age = 0 } }, 2, ) } From 064f028603a2461fba76cb96eab93da3f8e1990b Mon Sep 17 00:00:00 2001 From: Illyrius Date: Tue, 13 Jan 2026 18:08:52 +0100 Subject: [PATCH 50/64] refactor: remove entity scale randomization logic from EntityModule Signed-off-by: Illyrius --- .../vanillaplus/modules/EntityModule.kt | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt index 2352c2044..37fe32aa0 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt @@ -5,17 +5,14 @@ package org.xodium.vanillaplus.modules import io.papermc.paper.event.entity.EntityEquipmentChangedEvent import kotlinx.serialization.Serializable import org.bukkit.Material -import org.bukkit.attribute.Attribute import org.bukkit.entity.* import org.bukkit.event.EventHandler import org.bukkit.event.entity.EntityChangeBlockEvent import org.bukkit.event.entity.EntityDeathEvent import org.bukkit.event.entity.EntityExplodeEvent -import org.bukkit.event.entity.EntitySpawnEvent import org.bukkit.inventory.ItemStack import org.xodium.vanillaplus.enchantments.NimbusEnchantment import org.xodium.vanillaplus.interfaces.ModuleInterface -import org.xodium.vanillaplus.utils.Utils import kotlin.random.Random /** Represents a module handling entity mechanics within the system. */ @@ -40,9 +37,6 @@ internal object EntityModule : ModuleInterface { @EventHandler fun on(event: EntityEquipmentChangedEvent) = NimbusEnchantment.nimbus(event) - @EventHandler - fun on(event: EntitySpawnEvent) = randomizeEntityScale(event.entity) - /** * Determines whether an entity's griefing behaviour should be cancelled based on configuration settings. * @param entity The entity whose griefing behaviour is being evaluated. @@ -60,29 +54,6 @@ internal object EntityModule : ModuleInterface { else -> false } - /** - * Randomizes the scale of certain entities based on configuration settings. - * @param entity The entity whose scale should be randomized. - */ - private fun randomizeEntityScale(entity: Entity) { - when (entity) { - is Animals -> { - val range = config.entityModule.animalScaleRange ?: return - entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) - } - - is Monster -> { - val range = config.entityModule.monsterScaleRange ?: return - entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) - } - - is Villager -> { - val range = config.entityModule.villagerScaleRange ?: return - entity.getAttribute(Attribute.SCALE)?.baseValue = Random.nextDouble(range.min, range.max) - } - } - } - @Serializable data class Config( var enabled: Boolean = true, @@ -93,8 +64,5 @@ internal object EntityModule : ModuleInterface { var disableGhastGrief: Boolean = true, var disableWitherGrief: Boolean = true, var entityEggDropChance: Double = 0.001, - var animalScaleRange: Utils.Range? = Utils.Range(0.8, 1.2), - var monsterScaleRange: Utils.Range? = Utils.Range(0.9, 1.9), - var villagerScaleRange: Utils.Range? = Utils.Range(0.9, 1.1), ) } From 14bb3c47593c1c883167992d44119b583a03f3c5 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 14 Jan 2026 09:34:05 +0100 Subject: [PATCH 51/64] refactor: simplify nickname handling and scoreboard visibility logic Signed-off-by: Illyrius --- .../vanillaplus/modules/PlayerModule.kt | 4 ++-- .../vanillaplus/modules/ScoreBoardModule.kt | 4 ++-- .../org/xodium/vanillaplus/pdcs/PlayerPDC.kt | 22 +++++++------------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 0e0751c18..780d4aeab 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -106,7 +106,7 @@ internal object PlayerModule : ModuleInterface { private fun playerJoin(event: PlayerJoinEvent) { val player = event.player - player.displayName(MM.deserialize(player.nickname ?: player.name)) + player.displayName(MM.deserialize(player.nickname)) if (config.playerModule.i18n.playerJoinMsg .isEmpty() @@ -242,7 +242,7 @@ internal object PlayerModule : ModuleInterface { name: String, ) { player.nickname = name - player.displayName(MM.deserialize(player.nickname ?: player.name)) + player.displayName(MM.deserialize(player.nickname)) player.sendActionBar( MM.deserialize( config.playerModule.i18n.nicknameUpdated, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt index 79c0516e9..8411fae9a 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt @@ -45,7 +45,7 @@ internal object ScoreBoardModule : ModuleInterface { */ private fun playerJoin(event: PlayerJoinEvent) { event.player.scoreboard = - if (event.player.scoreboardVisibility == true) { + if (event.player.scoreboardVisibility) { instance.server.scoreboardManager.newScoreboard } else { instance.server.scoreboardManager.mainScoreboard @@ -57,7 +57,7 @@ internal object ScoreBoardModule : ModuleInterface { * @param player The player whose scoreboard sidebar should be toggled. */ private fun toggle(player: Player) { - if (player.scoreboardVisibility == true) { + if (player.scoreboardVisibility) { player.scoreboard = instance.server.scoreboardManager.mainScoreboard player.scoreboardVisibility = false } else { diff --git a/src/main/kotlin/org/xodium/vanillaplus/pdcs/PlayerPDC.kt b/src/main/kotlin/org/xodium/vanillaplus/pdcs/PlayerPDC.kt index c4146c5bb..cd27d8474 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/pdcs/PlayerPDC.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/pdcs/PlayerPDC.kt @@ -19,12 +19,12 @@ internal object PlayerPDC { /** * Gets or sets the player's nickname in their persistent data container. * @receiver The player whose nickname to access. - * @return The player's nickname, or null if not set. + * @return The player's nickname, or their actual name if no nickname is set. */ - var Player.nickname: String? - get() = persistentDataContainer.get(NICKNAME_KEY, PersistentDataType.STRING) + var Player.nickname: String + get() = persistentDataContainer.get(NICKNAME_KEY, PersistentDataType.STRING) ?: name set(value) { - if (value.isNullOrEmpty()) { + if (value.isBlank()) { persistentDataContainer.remove(NICKNAME_KEY) } else { persistentDataContainer.set(NICKNAME_KEY, PersistentDataType.STRING, value) @@ -34,15 +34,9 @@ internal object PlayerPDC { /** * Gets or sets the player's scoreboard visibility preference in their persistent data container. * @receiver The player whose scoreboard visibility to access. - * @return True if the scoreboard is visible, false if hidden, or null if not set. + * @return True if the scoreboard is visible, false otherwise. */ - var Player.scoreboardVisibility: Boolean? - get() = persistentDataContainer.get(SCOREBOARD_VISIBILITY_KEY, PersistentDataType.BOOLEAN) - set(value) { - if (value == null) { - persistentDataContainer.remove(SCOREBOARD_VISIBILITY_KEY) - } else { - persistentDataContainer.set(SCOREBOARD_VISIBILITY_KEY, PersistentDataType.BOOLEAN, value) - } - } + var Player.scoreboardVisibility: Boolean + get() = persistentDataContainer.get(SCOREBOARD_VISIBILITY_KEY, PersistentDataType.BOOLEAN) ?: false + set(value) = persistentDataContainer.set(SCOREBOARD_VISIBILITY_KEY, PersistentDataType.BOOLEAN, value) } From d51df6e88e709f13936ae43c41567d46a4c7454b Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 14 Jan 2026 10:13:12 +0100 Subject: [PATCH 52/64] feat: add 'sinv' command alias for inventory search and refactor search handling Signed-off-by: Illyrius --- .idea/dictionaries/project.xml | 1 + .../xodium/vanillaplus/modules/InventoryModule.kt | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index d0eec73bb..1fdb08485 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -39,6 +39,7 @@ searchinv shulker signedit + sinv slenderman spellbite tablist diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt index cf280dd95..263b9c371 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt @@ -40,7 +40,7 @@ internal object InventoryModule : ModuleInterface { .playerExecuted { _, ctx -> handleSearch(ctx) }, ).executesCatching { handleSearch(it) }, "Search nearby chests for specific items", - listOf("search", "searchinv", "invs"), + listOf("search", "searchinv", "invs", "sinv"), ), ) @@ -56,21 +56,19 @@ internal object InventoryModule : ModuleInterface { /** * Handles the search command execution. * @param ctx The command context containing the command source and arguments. - * @return An integer indicating the result of the command execution. */ - private fun handleSearch(ctx: CommandContext): Int { - val player = ctx.source.sender as? Player ?: return 0 + private fun handleSearch(ctx: CommandContext) { + val player = ctx.source.sender as? Player ?: return val material = runCatching { ctx.getArgument("material", ItemStack::class.java).type }.getOrNull() ?: player.inventory.itemInMainHand.type if (material == Material.AIR) { player.sendActionBar(MM.deserialize(config.inventoryModule.i18n.noMaterialSpecified)) - return 0 + return } - search(player, material) - return 1 + searchContainer(player, material) } /** @@ -78,7 +76,7 @@ internal object InventoryModule : ModuleInterface { * @param player The player who initiated the search. * @param material The material to search for in the chests. */ - private fun search( + private fun searchContainer( player: Player, material: Material, ) { From d6fef6d58be817e6e34d53cc032b38439ed2a6d5 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 14 Jan 2026 10:20:58 +0100 Subject: [PATCH 53/64] refactor: streamline inventory search command handling and remove redundant method Signed-off-by: Illyrius --- .../vanillaplus/modules/InventoryModule.kt | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt index 263b9c371..717b8739d 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt @@ -2,8 +2,6 @@ package org.xodium.vanillaplus.modules -import com.mojang.brigadier.context.CommandContext -import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.command.brigadier.argument.ArgumentTypes import kotlinx.serialization.Serializable @@ -20,7 +18,6 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.interfaces.ModuleInterface import org.xodium.vanillaplus.utils.BlockUtils.center -import org.xodium.vanillaplus.utils.CommandUtils.executesCatching import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.PlayerUtils.getContainersAround import org.xodium.vanillaplus.utils.ScheduleUtils @@ -37,8 +34,10 @@ internal object InventoryModule : ModuleInterface { .then( Commands .argument("material", ArgumentTypes.itemStack()) - .playerExecuted { _, ctx -> handleSearch(ctx) }, - ).executesCatching { handleSearch(it) }, + .playerExecuted { player, ctx -> + searchContainer(player, ctx.getArgument("material", ItemStack::class.java).type) + }, + ).playerExecuted { player, _ -> searchContainer(player, player.inventory.itemInMainHand.type) }, "Search nearby chests for specific items", listOf("search", "searchinv", "invs", "sinv"), ), @@ -53,24 +52,6 @@ internal object InventoryModule : ModuleInterface { ), ) - /** - * Handles the search command execution. - * @param ctx The command context containing the command source and arguments. - */ - private fun handleSearch(ctx: CommandContext) { - val player = ctx.source.sender as? Player ?: return - val material = - runCatching { ctx.getArgument("material", ItemStack::class.java).type }.getOrNull() - ?: player.inventory.itemInMainHand.type - - if (material == Material.AIR) { - player.sendActionBar(MM.deserialize(config.inventoryModule.i18n.noMaterialSpecified)) - return - } - - searchContainer(player, material) - } - /** * Searches for chests within the specified radius of the player that contain the specified material. * @param player The player who initiated the search. @@ -80,6 +61,11 @@ internal object InventoryModule : ModuleInterface { player: Player, material: Material, ) { + if (material == Material.AIR) { + player.sendActionBar(MM.deserialize(config.inventoryModule.i18n.noMaterialSpecified)) + return + } + val foundContainers = mutableListOf() for (container in player.getContainersAround()) { From c503c9fb0203f8ed78f3165a3b8238b7afcaa680 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 14 Jan 2026 10:29:43 +0100 Subject: [PATCH 54/64] cleanup Signed-off-by: Illyrius --- .github/workflows/codeql.yml | 63 ------------------------------------ 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 8f217eb9b..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: "CodeQL Advanced" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '33 16 * * 3' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - security-events: write - packages: read - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: java-kotlin - build-mode: manual - - steps: - - id: checkout - name: Checkout - uses: actions/checkout@v6.0.1 - - - id: setup-java - name: Setup Java - uses: actions/setup-java@v5 - with: - java-version: 21 - distribution: temurin - - - id: setup-gradle - name: Setup Gradle - uses: gradle/actions/setup-gradle@576fcbecfed70890e466eeffd7c78d93b30b0472 - - - id: init-codeql - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - id: build-gradle - name: Build with Gradle - if: matrix.build-mode == 'manual' - shell: bash - run: ./gradlew shadowJar - - - id: perform-codeql-analysis - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" From d9621c441cdbc758680c05de37e3161cb9b053da Mon Sep 17 00:00:00 2001 From: Illyrius Date: Wed, 14 Jan 2026 10:53:56 +0100 Subject: [PATCH 55/64] docs: add config documentation for various modules Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/modules/BooksModule.kt | 1 + src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt | 2 ++ .../kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt | 2 ++ src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt | 1 + .../kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt | 2 ++ .../kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt | 1 + src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt | 1 + .../kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt | 1 + src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt | 3 +++ .../kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt | 1 + src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt | 1 + src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt | 1 + .../kotlin/org/xodium/vanillaplus/modules/TabListModule.kt | 2 ++ .../kotlin/org/xodium/vanillaplus/modules/TameableModule.kt | 1 + 14 files changed, 20 insertions(+) diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/BooksModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/BooksModule.kt index 410065fd3..f76027392 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/BooksModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/BooksModule.kt @@ -33,6 +33,7 @@ internal object BooksModule : ModuleInterface { ) } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt index 36b14d7a7..a5b295c10 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ChatModule.kt @@ -197,6 +197,7 @@ internal object ChatModule : ModuleInterface { .hoverEvent(MM.deserialize(config.chatModule.i18n.deleteMessage)) .clickEvent(ClickEvent.callback { instance.server.deleteMessage(signedMessage) }) + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, @@ -221,6 +222,7 @@ internal object ChatModule : ModuleInterface { var deleteCross: String = "[X]", var i18n: I18n = I18n(), ) { + /** Represents the internationalization strings for the module. */ @Serializable data class I18n( var clickMe: String = "Click me!", diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt index cf52c9d57..da0b991da 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/DimensionsModule.kt @@ -126,12 +126,14 @@ internal object DimensionsModule : ModuleInterface { */ private fun getOverworld(): World = instance.server.getWorld("world") ?: error("Overworld (world) is not loaded.") + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, var portalSearchRadius: Int = 128, var i18n: I18n = I18n(), ) { + /** Represents the internationalization strings for the module. */ @Serializable data class I18n( var portalCreationDenied: String = diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt index 37fe32aa0..ba8d45bf6 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/EntityModule.kt @@ -54,6 +54,7 @@ internal object EntityModule : ModuleInterface { else -> false } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt index 717b8739d..82f7270b7 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/InventoryModule.kt @@ -108,11 +108,13 @@ internal object InventoryModule : ModuleInterface { } } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, var i18n: I18n = I18n(), ) { + /** Represents the internationalization strings for the module. */ @Serializable data class I18n( var noMaterialSpecified: String = diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt index e157f1a39..b1d4e64b3 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/LocatorModule.kt @@ -54,6 +54,7 @@ internal object LocatorModule : ModuleInterface { ), ) + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt index 62b5079cc..9a90bdb02 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/MotdModule.kt @@ -18,6 +18,7 @@ internal object MotdModule : ModuleInterface { */ private fun motd(event: ServerListPingEvent) = event.motd(MM.deserialize(config.motdModule.motd.joinToString("\n"))) + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt index ffd877d37..0e4e3d17c 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/OpenableModule.kt @@ -229,6 +229,7 @@ internal object OpenableModule : ModuleInterface { ?.getRelativeBlock(block) } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 780d4aeab..268e2b0e4 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -251,6 +251,7 @@ internal object PlayerModule : ModuleInterface { ) } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, @@ -260,12 +261,14 @@ internal object PlayerModule : ModuleInterface { var silkTouch: SilkTouchEnchantment = SilkTouchEnchantment(), var i18n: I18n = I18n(), ) { + /** Represents the settings for the Silk Touch enchantment. */ @Serializable data class SilkTouchEnchantment( var allowSpawnerSilk: Boolean = true, var allowBuddingAmethystSilk: Boolean = true, ) + /** Represents the internationalization strings for the module. */ @Serializable data class I18n( // var playerDeathMsg: String = " ", diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt index 8411fae9a..df54d21ec 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ScoreBoardModule.kt @@ -66,6 +66,7 @@ internal object ScoreBoardModule : ModuleInterface { } } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt index c1f176f8a..1dbac6783 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SignModule.kt @@ -69,6 +69,7 @@ internal object SignModule : ModuleInterface { sign.update() } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt index 2922d6e5e..21733b83e 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/SitModule.kt @@ -193,6 +193,7 @@ internal object SitModule : ModuleInterface { occupiedBlocks[blockLocation] = playerId } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt index b7fc05e87..3d33b9cb2 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TabListModule.kt @@ -98,6 +98,7 @@ internal object TabListModule : ModuleInterface { } } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, @@ -115,6 +116,7 @@ internal object TabListModule : ModuleInterface { ), var i18n: I18n = I18n(), ) { + /** Represents the internationalization strings for the module. */ @Serializable data class I18n( var weatherThundering: String = "\uD83C\uDF29", diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt index af1d588f0..397ee951f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/TameableModule.kt @@ -34,6 +34,7 @@ internal object TameableModule : ModuleInterface { event.isCancelled = true } + /** Represents the config of the module. */ @Serializable data class Config( var enabled: Boolean = true, From 16430558538a93e8c5f332e0f6a949d2dc92171f Mon Sep 17 00:00:00 2001 From: Illyrius Date: Thu, 15 Jan 2026 08:48:21 +0100 Subject: [PATCH 56/64] feat: add website URL to paperPluginYaml configuration Signed-off-by: Illyrius --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 53a378242..37ad12288 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,7 @@ tasks { paperPluginYaml { main.set(group.toString()) + website.set("https://github.com/XodiumSoftware/VanillaPlus") authors.add("Xodium") apiVersion.set(version) bootstrapper.set("org.xodium.vanillaplus.VanillaPlusBootstrap") From d5b645f8cf92a426dcc4ff264ddc35d233880cb9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:08:09 +0000 Subject: [PATCH 57/64] [ci-skip] Update Gradle to v9.3.0 (#361) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b5..19a6bdeb8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f0a83d1c0f9c8f733186bec1341661b2d1dfdbc9 Mon Sep 17 00:00:00 2001 From: Illyrius Date: Tue, 20 Jan 2026 07:53:07 +0100 Subject: [PATCH 58/64] refactor: replace sequential guard clauses with when expression in featherFalling handler Signed-off-by: Illyrius --- .../enchantments/FeatherFallingEnchantment.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt b/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt index f82462be3..1026e1307 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/enchantments/FeatherFallingEnchantment.kt @@ -15,11 +15,12 @@ internal object FeatherFallingEnchantment : EnchantmentInterface { * @param event The PlayerInteractEvent to handle. */ fun featherFalling(event: PlayerInteractEvent) { - if (event.action != Action.PHYSICAL) return - if (event.clickedBlock?.type != Material.FARMLAND) return - if (!isValidTool(event.player.inventory.boots)) return - - event.isCancelled = true + when { + event.action != Action.PHYSICAL -> return + event.clickedBlock?.type != Material.FARMLAND -> return + !isValidTool(event.player.inventory.boots) -> return + else -> event.isCancelled = true + } } /** From 51fe3678a14895258e1a3337baad90d114ba4038 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:32:51 +0000 Subject: [PATCH 59/64] [ci-skip] Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.10.0 (#363) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 37ad12288..a8eba3472 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { implementation(kotlin("stdlib")) implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0") } java { From cbdde504e8680f3ab442cc75aa2e302254aa0200 Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:58:15 +0100 Subject: [PATCH 60/64] feat/ArmorStandModule (#362) * init Signed-off-by: Illyrius * feat: implement armor stand interaction handling Signed-off-by: Illyrius * feat: update armor stand interaction to use InventoryView and improve event handling Signed-off-by: Illyrius * feat: add menu creation for ArmorStand interactions Signed-off-by: Illyrius * feat: enhance armor stand inventory handling with improved event management and fill functionality Signed-off-by: Illyrius * oops Signed-off-by: Illyrius * feat: implement toggle functionality for armor stand properties in inventory Signed-off-by: Illyrius * feat: enhance toggle item creation and internationalization for armor stand properties Signed-off-by: Illyrius * feat: update armor stand item slots and improve inventory event handling Signed-off-by: Illyrius * feat: refine armor stand inventory handling and add manipulation event support Signed-off-by: Illyrius * feat: refactor armor stand manipulation logic and rename inventory handler for clarity Signed-off-by: Illyrius * feat: restructure armor stand event handling and improve method naming for clarity Signed-off-by: Illyrius * feat: simplify armor stand item transfer logic and remove redundant hand checks Signed-off-by: Illyrius * feat: adjust armor stand menu trigger and update code style settings Signed-off-by: Illyrius * forgot to add todo task Signed-off-by: Illyrius * feat: redesign armor stand menu layout, add fill item support, and update equipment slot handling Signed-off-by: Illyrius * fix: restrict armor stand menu interactions to top inventory and improve event cancellation logic Signed-off-by: Illyrius * oops Signed-off-by: Illyrius * feat: add handler stub for armor stand menu drag events Signed-off-by: Illyrius * refactor: remove unused InventoryDragEvent handler and add shift-click handling for armor stand equipment slots Signed-off-by: Illyrius * refactor: extract armor stand equipment swap logic into helper function Signed-off-by: Illyrius * fix: prevent premature event cancellation in armor stand equipment handler Signed-off-by: Illyrius * refactor: inline armor stand equipment swap logic and remove helper function Signed-off-by: Illyrius * chore: add todo for blocking item stacking in hand slots in armor stand menu Signed-off-by: Illyrius * fix: cancel shift-clicking in armor stand menu to prevent unintended item movement Signed-off-by: Illyrius --------- Signed-off-by: Illyrius Signed-off-by: Illyrius --- .idea/codeStyles/Project.xml | 1 + .idea/dictionaries/project.xml | 1 + .../org/xodium/vanillaplus/VanillaPlus.kt | 1 + .../org/xodium/vanillaplus/data/ConfigData.kt | 1 + .../vanillaplus/modules/ArmorStandModule.kt | 289 ++++++++++++++++++ 5 files changed, 293 insertions(+) create mode 100644 src/main/kotlin/org/xodium/vanillaplus/modules/ArmorStandModule.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 1bec35e57..6c39b1b83 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,7 @@ + diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 1fdb08485..ffe3b8db7 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -4,6 +4,7 @@ ageable appleboy armorposer + armorstand birdflop cmds codeql diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index cad541676..1b5aa595c 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -49,6 +49,7 @@ internal class VanillaPlus : JavaPlugin() { ).forEach { module -> module.register() } listOfNotNull( + ArmorStandModule, BooksModule, ChatModule, DimensionsModule, diff --git a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt index e2a7b8c92..24f46ba38 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/data/ConfigData.kt @@ -23,6 +23,7 @@ import kotlin.time.measureTime /** Configuration data for the plugin. */ @Serializable internal data class ConfigData( + var armorStandModule: ArmorStandModule.Config = ArmorStandModule.Config(), var booksModule: BooksModule.Config = BooksModule.Config(), var chatModule: ChatModule.Config = ChatModule.Config(), var dimensionsModule: DimensionsModule.Config = DimensionsModule.Config(), diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/ArmorStandModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/ArmorStandModule.kt new file mode 100644 index 000000000..35ec722d2 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/ArmorStandModule.kt @@ -0,0 +1,289 @@ +@file:Suppress("ktlint:standard:no-wildcard-imports") + +package org.xodium.vanillaplus.modules + +import io.papermc.paper.datacomponent.DataComponentTypes +import io.papermc.paper.datacomponent.item.TooltipDisplay +import kotlinx.serialization.Serializable +import org.bukkit.Material +import org.bukkit.entity.ArmorStand +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.player.PlayerInteractAtEntityEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.MenuType +import org.xodium.vanillaplus.interfaces.ModuleInterface +import org.xodium.vanillaplus.utils.Utils.MM +import java.util.* + +/** Represents a module handling armor stand mechanics within the system. */ +internal object ArmorStandModule : ModuleInterface { + private val armorStandViews = WeakHashMap() + + // Inventory slot constants for armorstand equipment + private const val ARMOR_STAND_MAIN_HAND_SLOT = 0 + private const val ARMOR_STAND_OFF_HAND_SLOT = 1 + + // Inventory slot constants for armorstand properties + private const val ARMOR_STAND_NAME_TAG_ITEM_SLOT = 3 + private const val ARMOR_STAND_ARMS_ITEM_SLOT = 4 + private const val ARMOR_STAND_SMALL_ITEM_SLOT = 5 + private const val ARMOR_STAND_BASEPLATE_ITEM_SLOT = 6 + private const val ARMOR_STAND_MORE_OPTIONS_SLOT = 8 + + @EventHandler + private fun on(event: PlayerInteractAtEntityEvent) = handleArmorStandMenu(event) + + @EventHandler + fun on(event: InventoryClickEvent) = handleArmorStandMenuClicking(event) + + @EventHandler + fun on(event: InventoryCloseEvent) = handleArmorStandCleanup(event) + + /** + * Handles the interaction with an ArmorStand's inventory. + * @param event EntityInteractEvent The event triggered by the interaction. + */ + private fun handleArmorStandMenu(event: PlayerInteractAtEntityEvent) { + val armorStand = event.rightClicked as? ArmorStand ?: return + + if (!event.player.isSneaking) return + + event.isCancelled = true + + val view = armorStand.menu(event.player) + + armorStandViews[view] = armorStand + view.open() + } + + /** + * Handles the interaction with ArmorStand slots in the inventory. + * @param event InventoryClickEvent The event triggered by the inventory click. + */ + private fun handleArmorStandMenuClicking(event: InventoryClickEvent) { + val armorStand = armorStandViews[event.view] ?: return + + if (event.click.isShiftClick) event.isCancelled = true // TODO: Handle shift-clicking better in v2. + if (event.clickedInventory != event.view.topInventory) return + + when (event.slot) { + // Equipment Slots + ARMOR_STAND_MAIN_HAND_SLOT -> { + armorStand.equipment.setItemInMainHand(event.cursor) + } + + ARMOR_STAND_OFF_HAND_SLOT -> { + armorStand.equipment.setItemInOffHand(event.cursor) + } + + // Properties Slots + ARMOR_STAND_NAME_TAG_ITEM_SLOT -> { + event.isCancelled = true + toggleArmorStandProperty( + armorStand, + event.inventory, + ARMOR_STAND_NAME_TAG_ITEM_SLOT, + { stand -> stand.isCustomNameVisible }, + { stand, value -> stand.isCustomNameVisible = value }, + ) + } + + ARMOR_STAND_ARMS_ITEM_SLOT -> { + event.isCancelled = true + toggleArmorStandProperty( + armorStand, + event.inventory, + ARMOR_STAND_ARMS_ITEM_SLOT, + { stand -> stand.hasArms() }, + { stand, value -> stand.setArms(value) }, + ) + } + + ARMOR_STAND_SMALL_ITEM_SLOT -> { + event.isCancelled = true + toggleArmorStandProperty( + armorStand, + event.inventory, + ARMOR_STAND_SMALL_ITEM_SLOT, + { stand -> stand.isSmall }, + { stand, value -> stand.isSmall = value }, + ) + } + + ARMOR_STAND_BASEPLATE_ITEM_SLOT -> { + event.isCancelled = true + toggleArmorStandProperty( + armorStand, + event.inventory, + ARMOR_STAND_BASEPLATE_ITEM_SLOT, + { stand -> stand.hasBasePlate() }, + { stand, value -> stand.setBasePlate(value) }, + ) + } + + else -> { + event.isCancelled = true + } + } + } + + /** + * Cleans up the armor stand view mapping when the inventory is closed. + * @param event InventoryCloseEvent The event triggered by the inventory close. + */ + private fun handleArmorStandCleanup(event: InventoryCloseEvent) { + armorStandViews.remove(event.view) + } + + /** + * Toggles a boolean property of the [ArmorStand] and updates the corresponding inventory item. + * @param armorStand [ArmorStand] The ArmorStand whose property is to be toggled. + * @param inventory [Inventory] The inventory where the toggle item is located. + * @param slot Int The slot index of the toggle item in the inventory. + * @param getCurrentState Function to get the current state of the property. + * @param setState Function to set the new state of the property. + */ + @Suppress("UnstableApiUsage") + private fun toggleArmorStandProperty( + armorStand: ArmorStand, + inventory: Inventory, + slot: Int, + getCurrentState: (ArmorStand) -> Boolean, + setState: (ArmorStand, Boolean) -> Unit, + ) { + val newState = !getCurrentState(armorStand) + val currentItem = inventory.getItem(slot) ?: return + val displayName = currentItem.getData(DataComponentTypes.ITEM_NAME) + + setState(armorStand, newState) + inventory.setItem( + slot, + ItemStack.of(if (newState) Material.GREEN_WOOL else Material.RED_WOOL).apply { + displayName?.let { setData(DataComponentTypes.ITEM_NAME, it) } + }, + ) + } + + /** + * Creates a toggle item with the specified material and display name. + * @param material [Material] The material of the item. + * @param displayName String The display name of the item. + * @return [ItemStack] The created toggle item. + */ + @Suppress("UnstableApiUsage") + private fun createToggleItem( + material: Material, + displayName: String, + ): ItemStack = ItemStack.of(material).apply { setData(DataComponentTypes.ITEM_NAME, MM.deserialize(displayName)) } + + /** + * Creates a menu for the given ArmorStand and Player. + * @receiver ArmorStand The ArmorStand for which the menu is created. + * @param player Player The player for whom the menu is created. + * @return InventoryView The created menu view. + */ + @Suppress("UnstableApiUsage") + private fun ArmorStand.menu(player: Player): InventoryView = + MenuType + .GENERIC_9X1 + .builder() + .title(customName() ?: MM.deserialize(name)) + .build(player) + .apply { + topInventory + .apply { + fill() + + // Equipment Slots + setItem(ARMOR_STAND_MAIN_HAND_SLOT, equipment.itemInMainHand) + setItem(ARMOR_STAND_OFF_HAND_SLOT, equipment.itemInOffHand) + + // Property Slots + setItem( + ARMOR_STAND_NAME_TAG_ITEM_SLOT, + createToggleItem( + if (isCustomNameVisible) Material.GREEN_WOOL else Material.RED_WOOL, + config.armorStandModule.i18n.toggleNameTagVisibility, + ), + ) + setItem( + ARMOR_STAND_ARMS_ITEM_SLOT, + createToggleItem( + if (hasArms()) Material.GREEN_WOOL else Material.RED_WOOL, + config.armorStandModule.i18n.toggleArmsVisibility, + ), + ) + setItem( + ARMOR_STAND_SMALL_ITEM_SLOT, + createToggleItem( + if (isSmall) Material.GREEN_WOOL else Material.RED_WOOL, + config.armorStandModule.i18n.toggleSmallArmorStand, + ), + ) + setItem( + ARMOR_STAND_BASEPLATE_ITEM_SLOT, + createToggleItem( + if (hasBasePlate()) Material.GREEN_WOOL else Material.RED_WOOL, + config.armorStandModule.i18n.toggleBasePlateVisibility, + ), + ) + setItem( + ARMOR_STAND_MORE_OPTIONS_SLOT, + ItemStack.of(Material.ARMOR_STAND).apply { + setData( + DataComponentTypes.CUSTOM_NAME, + MM.deserialize(config.armorStandModule.i18n.moreOptionsComingSoon), + ) + }, + ) + } + } + + /** + * Fills the inventory with the configured fill item. + * @receiver Inventory The inventory to be filled. + */ + private fun Inventory.fill() { + for (i in 0 until size) { + setItem( + i, + @Suppress("UnstableApiUsage") + ItemStack.of(config.armorStandModule.menuFillItemMaterial).apply { + setData( + DataComponentTypes.TOOLTIP_DISPLAY, + TooltipDisplay.tooltipDisplay().hideTooltip(config.armorStandModule.menuFIllItemTooltip), + ) + }, + ) + } + } + + /** Represents the config of the module. */ + @Serializable + data class Config( + var enabled: Boolean = true, + var menuFillItemMaterial: Material = Material.BLACK_STAINED_GLASS_PANE, + var menuFIllItemTooltip: Boolean = true, + var i18n: I18n = I18n(), + ) { + /** Represents the internationalization settings of the module. */ + @Serializable + data class I18n( + var toggleNameTagVisibility: String = + "Toggle Name Tag Visibility", + var toggleArmsVisibility: String = + "Toggle Arms Visibility", + var toggleSmallArmorStand: String = + "Toggle Small ArmorStand", + var toggleBasePlateVisibility: String = + "Toggle Base Plate Visibility", + var moreOptionsComingSoon: String = + "More Options Coming Soon!", + ) + } +} From b1aa93cce948906dd253f0b5932e2088db2faa70 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:32:14 +0000 Subject: [PATCH 61/64] [ci-skip] Update actions/checkout action to v6.0.2 (#364) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml index ab4521a93..fafa67f83 100644 --- a/.github/workflows/build_app.yml +++ b/.github/workflows/build_app.yml @@ -19,7 +19,7 @@ jobs: steps: - id: checkout name: Checkout - uses: actions/checkout@v6.0.1 + uses: actions/checkout@v6.0.2 - id: setup-java name: Setup Java From 6a3a61b529ebc2086834e5a5e4c29f2dbc5cb78b Mon Sep 17 00:00:00 2001 From: Illyrius <28700752+illyrius666@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:42:24 +0100 Subject: [PATCH 62/64] fix/PlayerModule/EventMessages (#368) * init Signed-off-by: Illyrius * init Signed-off-by: Illyrius * refactor: improve event message handling in PlayerModule and PlayerMessageManager - Ensure event messages are only set when non-null by using early returns - Update handleDeath to distinguish between player and player-killed deaths, using appropriate messages - Rename enderchest handler to handleEnderchest for clarity Signed-off-by: Illyrius * refactor: remove unused playerDeathMsg config and simplify death message handling in PlayerMessageManager Signed-off-by: Illyrius --------- Signed-off-by: Illyrius --- .../managers/PlayerMessageManager.kt | 94 ++++++++++++ .../vanillaplus/modules/PlayerModule.kt | 142 ++++++------------ 2 files changed, 138 insertions(+), 98 deletions(-) create mode 100644 src/main/kotlin/org/xodium/vanillaplus/managers/PlayerMessageManager.kt diff --git a/src/main/kotlin/org/xodium/vanillaplus/managers/PlayerMessageManager.kt b/src/main/kotlin/org/xodium/vanillaplus/managers/PlayerMessageManager.kt new file mode 100644 index 000000000..2bbc5db29 --- /dev/null +++ b/src/main/kotlin/org/xodium/vanillaplus/managers/PlayerMessageManager.kt @@ -0,0 +1,94 @@ +package org.xodium.vanillaplus.managers + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import org.bukkit.advancement.Advancement +import org.bukkit.entity.Player +import org.xodium.vanillaplus.modules.PlayerModule +import org.xodium.vanillaplus.utils.Utils.MM + +/** Manages player messages and internationalization. */ +internal object PlayerMessageManager { + private val config = PlayerModule.config.playerModule.i18n + + /** + * Handles the player join message. + * @param player The player who joined. + * @return The formatted join message component, or null if no message is set. + */ + fun handleJoin(player: Player): Component? { + if (config.playerJoinMsg.isEmpty()) return null + + return MM.deserialize(config.playerJoinMsg, Placeholder.component("player", player.displayName())) + } + + /** + * Handles the player leave message. + * @param player The player who left. + * @return The formatted leave message component, or null if no message is set. + */ + fun handleQuit(player: Player): Component? { + if (config.playerQuitMsg.isEmpty()) return null + + return MM.deserialize(config.playerQuitMsg, Placeholder.component("player", player.displayName())) + } + + /** + * Handles the player death message. + * @param player The player who died. + * @param killer The player who killed them. + * @return The formatted death message component, or null if no message is set. + */ + fun handleDeath( + player: Player, + killer: Player?, + ): Component? { + if (config.playerDeathByPlayerMsg.isEmpty() || killer == null) return null + + return MM.deserialize( + config.playerDeathByPlayerMsg, + Placeholder.component("player", player.displayName()), + Placeholder.component("killer", killer.displayName()), + ) + } + + /** + * Handles the player death screen message. + * @return The formatted death screen message component, or null if no message is set. + */ + fun handleDeathScreen(): Component? { + if (config.playerDeathScreenMsg.isEmpty()) return null + + return MM.deserialize(config.playerDeathScreenMsg) + } + + /** + * Handles the player advancement completion message. + * @param player The player who completed the advancement. + * @param advancement The advancement that was completed. + * @return The formatted advancement completion message component, or null if no message is set. + */ + fun handleAdvancement( + player: Player, + advancement: Advancement, + ): Component? { + if (config.playerAdvancementDoneMsg.isEmpty()) return null + + return MM.deserialize( + config.playerAdvancementDoneMsg, + Placeholder.component("player", player.displayName()), + Placeholder.component("advancement", advancement.displayName()), + ) + } + + /** + * Handles the player kick message. + * @param reason The reason for the kick. + * @return The formatted kick message component, or null if no message is set. + */ + fun handleKick(reason: Component): Component? { + if (config.playerKickMsg.isEmpty()) return null + + return MM.deserialize(config.playerKickMsg, Placeholder.component("reason", reason)) + } +} diff --git a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt index 268e2b0e4..57a682d0f 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/modules/PlayerModule.kt @@ -19,10 +19,7 @@ import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryType -import org.bukkit.event.player.PlayerAdvancementDoneEvent -import org.bukkit.event.player.PlayerInteractEvent -import org.bukkit.event.player.PlayerJoinEvent -import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.event.player.* import org.bukkit.inventory.ItemStack import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault @@ -30,6 +27,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.enchantments.* import org.xodium.vanillaplus.interfaces.ModuleInterface +import org.xodium.vanillaplus.managers.PlayerMessageManager import org.xodium.vanillaplus.pdcs.PlayerPDC.nickname import org.xodium.vanillaplus.utils.CommandUtils.playerExecuted import org.xodium.vanillaplus.utils.Utils.MM @@ -66,19 +64,35 @@ internal object PlayerModule : ModuleInterface { ) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerJoinEvent) = playerJoin(event) + fun on(event: PlayerJoinEvent) { + event.player.setNickname() + event.joinMessage(PlayerMessageManager.handleJoin(event.player) ?: return) + } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerQuitEvent) = playerQuit(event) + fun on(event: PlayerQuitEvent) { + event.quitMessage(PlayerMessageManager.handleQuit(event.player) ?: return) + } + + @EventHandler + fun on(event: PlayerKickEvent) { + event.leaveMessage(PlayerMessageManager.handleKick(event.reason()) ?: return) + } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - fun on(event: PlayerDeathEvent) = playerDeath(event) + fun on(event: PlayerDeathEvent) { + dropPlayerHead(event.player) + event.deathMessage(PlayerMessageManager.handleDeath(event.player, event.entity.killer) ?: return) + event.deathScreenMessageOverride(PlayerMessageManager.handleDeathScreen()) + } @EventHandler - fun on(event: PlayerAdvancementDoneEvent) = playerAdvancementDone(event) + fun on(event: PlayerAdvancementDoneEvent) { + event.message(PlayerMessageManager.handleAdvancement(event.player, event.advancement) ?: return) + } @EventHandler - fun on(event: InventoryClickEvent) = enderchest(event) + fun on(event: InventoryClickEvent) = handleEnderchest(event) @EventHandler(ignoreCancelled = true) fun on(event: PlayerInteractEvent) { @@ -99,91 +113,14 @@ internal object PlayerModule : ModuleInterface { @EventHandler fun on(event: EntityEquipmentChangedEvent) = NightVisionEnchantment.nightVision(event) - /** - * Handles the event when a player joins the game. - * @param event The PlayerJoinEvent triggered when a player joins. - */ - private fun playerJoin(event: PlayerJoinEvent) { - val player = event.player - - player.displayName(MM.deserialize(player.nickname)) - - if (config.playerModule.i18n.playerJoinMsg - .isEmpty() - ) { - return - } - - event.joinMessage(null) - - instance.server.onlinePlayers - .filter { it.uniqueId != player.uniqueId } - .forEach { - it.sendMessage( - MM.deserialize( - config.playerModule.i18n.playerJoinMsg, - Placeholder.component("player", player.displayName()), - ), - ) - } - } - /** * Handles the event when a player dies. * @param event The PlayerDeathEvent triggered when a player dies. */ - private fun playerDeath(event: PlayerDeathEvent) { - if (Random.nextDouble() <= config.playerModule.skullDropChance) { - event.entity.world.dropItemNaturally( - event.entity.location, - @Suppress("UnstableApiUsage") - ItemStack.of(Material.PLAYER_HEAD).apply { - setData(DataComponentTypes.PROFILE, ResolvableProfile.resolvableProfile(event.entity.playerProfile)) - }, - ) - } - // TODO -// if (config.playerFeature.i18n.playerDeathMsg.isNotEmpty()) event.deathMessage() -// if (config.playerFeature.i18n.playerDeathScreenMsg.isNotEmpty()) event.deathScreenMessageOverride() - } + private fun dropPlayerHead(player: Player) { + if (Random.nextDouble() > config.playerModule.skullDropChance) return - /** - * Handles the event when a player quits the game. - * @param event The PlayerQuitEvent triggered when a player quits the game. - */ - private fun playerQuit(event: PlayerQuitEvent) { - if (config.playerModule.i18n.playerQuitMsg - .isEmpty() - ) { - return - } - - event.quitMessage( - MM.deserialize( - config.playerModule.i18n.playerQuitMsg, - Placeholder.component("player", event.player.displayName()), - ), - ) - } - - /** - * Handles the event when a player completes an advancement. - * @param event The PlayerAdvancementDoneEvent triggered when a player completes an advancement. - */ - private fun playerAdvancementDone(event: PlayerAdvancementDoneEvent) { - if (config.playerModule.i18n.playerAdvancementDoneMsg - .isEmpty() - ) { - return - } - - event.message( - MM.deserialize( - config.playerModule.i18n.playerAdvancementDoneMsg, - Placeholder.component("player", event.player.displayName()), - Placeholder.component("advancement", event.advancement.displayName()), - ), - ) + player.world.dropItemNaturally(player.location, player.head()) } /** @@ -191,13 +128,10 @@ internal object PlayerModule : ModuleInterface { * in their inventory. * @param event The InventoryClickEvent triggered when a player clicks in an inventory. */ - private fun enderchest(event: InventoryClickEvent) { - if (event.click != config.playerModule.enderChestClickType || - event.currentItem?.type != Material.ENDER_CHEST || - event.clickedInventory?.type != InventoryType.PLAYER - ) { - return - } + private fun handleEnderchest(event: InventoryClickEvent) { + if (event.click != config.playerModule.enderChestClickType) return + if (event.currentItem?.type != Material.ENDER_CHEST) return + if (event.clickedInventory?.type != InventoryType.PLAYER) return event.isCancelled = true @@ -251,6 +185,16 @@ internal object PlayerModule : ModuleInterface { ) } + /** Sets the display name of the player based on their nickname. */ + private fun Player.setNickname() = displayName(MM.deserialize(nickname)) + + /** Drops the player's head at their location based on the configured chance. */ + @Suppress("UnstableApiUsage") + private fun Player.head(): ItemStack = + ItemStack.of(Material.PLAYER_HEAD).apply { + setData(DataComponentTypes.PROFILE, ResolvableProfile.resolvableProfile(playerProfile)) + } + /** Represents the config of the module. */ @Serializable data class Config( @@ -271,14 +215,16 @@ internal object PlayerModule : ModuleInterface { /** Represents the internationalization strings for the module. */ @Serializable data class I18n( -// var playerDeathMsg: String = " ", var playerJoinMsg: String = " ", var playerQuitMsg: String = " ", - var playerDeathMsg: String = "☠ ›", + var playerDeathByPlayerMsg: String = " ", var playerDeathScreenMsg: String = "☠", var playerAdvancementDoneMsg: String = "\uD83C\uDF89 " + "has made the advancement: ", + var playerKickMsg: String = + " " + + "reason: ", var nicknameUpdated: String = "Nickname has been updated to: ", ) From 02ae1a10735630ae219e5d130cd7acde68441eca Mon Sep 17 00:00:00 2001 From: Illyrius Date: Fri, 23 Jan 2026 14:34:57 +0100 Subject: [PATCH 63/64] refactor: unify and optimize module and recipe registration logging - Refactored ModuleInterface and RecipeInterface to return registration time and provide logger extension functions for batch logging. - Updated VanillaPlus to use new logging methods for module and recipe registration. - Improved code clarity and reduced duplication in registration logic. Signed-off-by: Illyrius --- .../org/xodium/vanillaplus/VanillaPlus.kt | 50 ++++++++++-------- .../vanillaplus/interfaces/ModuleInterface.kt | 51 +++++++++++-------- .../vanillaplus/interfaces/RecipeInterface.kt | 21 +++++--- 3 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 1b5aa595c..46e0c5726 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -7,7 +7,9 @@ import org.bukkit.plugin.java.JavaPlugin import org.xodium.vanillaplus.data.ConfigData import org.xodium.vanillaplus.data.ConfigData.Companion.load import org.xodium.vanillaplus.modules.* +import org.xodium.vanillaplus.modules.ArmorStandModule.info import org.xodium.vanillaplus.recipes.PaintingRecipe +import org.xodium.vanillaplus.recipes.PaintingRecipe.info import org.xodium.vanillaplus.recipes.RottenFleshRecipe import org.xodium.vanillaplus.recipes.WoodLogRecipe @@ -42,29 +44,33 @@ internal class VanillaPlus : JavaPlugin() { configData = ConfigData().load("config.json") - listOf( - PaintingRecipe, - RottenFleshRecipe, - WoodLogRecipe, - ).forEach { module -> module.register() } + logger.info( + listOf( + PaintingRecipe, + RottenFleshRecipe, + WoodLogRecipe, + ), + ) - listOfNotNull( - ArmorStandModule, - BooksModule, - ChatModule, - DimensionsModule, - EntityModule, - InventoryModule, - LocatorModule, - MotdModule, - OpenableModule, - PlayerModule, - ScoreBoardModule, - SignModule, - SitModule, - TabListModule, - TameableModule, - ).forEach { module -> module.register() } + logger.info( + listOf( + ArmorStandModule, + BooksModule, + ChatModule, + DimensionsModule, + EntityModule, + InventoryModule, + LocatorModule, + MotdModule, + OpenableModule, + PlayerModule, + ScoreBoardModule, + SignModule, + SitModule, + TabListModule, + TameableModule, + ), + ) } /** diff --git a/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt b/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt index 7acf6f14f..94c494ea3 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/interfaces/ModuleInterface.kt @@ -7,6 +7,7 @@ import org.xodium.vanillaplus.VanillaPlus.Companion.configData import org.xodium.vanillaplus.VanillaPlus.Companion.instance import org.xodium.vanillaplus.data.CommandData import org.xodium.vanillaplus.data.ConfigData +import java.util.logging.Logger import kotlin.reflect.full.memberProperties import kotlin.time.measureTime @@ -32,7 +33,7 @@ internal interface ModuleInterface : Listener { ?.let { moduleConfig -> moduleConfig::class .memberProperties - .firstOrNull { prop -> prop.name == "enabled" } + .firstOrNull { it.name == "enabled" } ?.call(moduleConfig) as? Boolean } ?: true @@ -49,27 +50,35 @@ internal interface ModuleInterface : Listener { */ val perms: List get() = emptyList() - /** Registers this feature as an event listener with the server. */ + /** + * Registers this feature as an event listener with the server. + * @return The time taken to register the feature in milliseconds, or null if the feature is disabled. + */ @Suppress("UnstableApiUsage") - fun register() { - if (!isEnabled) return + fun register(): Long? { + if (!isEnabled) return null - instance.logger.info( - "Registering: ${this::class.simpleName} | Took ${ - measureTime { - instance.server.pluginManager.registerEvents(this, instance) - instance.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { event -> - cmds.forEach { cmd -> - event.registrar().register( - cmd.builder.build(), - cmd.description, - cmd.aliases, - ) - } - } - instance.server.pluginManager.addPermissions(perms) - }.inWholeMilliseconds - }ms", - ) + return measureTime { + instance.server.pluginManager.registerEvents(this, instance) + instance.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { event -> + cmds.forEach { cmd -> + event.registrar().register( + cmd.builder.build(), + cmd.description, + cmd.aliases, + ) + } + } + instance.server.pluginManager.addPermissions(perms) + }.inWholeMilliseconds + } + + /** + * Logs the registration details of a list of modules. + * @receiver Logger The logger to use for logging. + * @param modules List of [ModuleInterface] instances to log. + */ + fun Logger.info(modules: List) { + info("Registered: ${modules.size} module(s) | Took ${modules.sumOf { it.register() ?: return }}ms") } } diff --git a/src/main/kotlin/org/xodium/vanillaplus/interfaces/RecipeInterface.kt b/src/main/kotlin/org/xodium/vanillaplus/interfaces/RecipeInterface.kt index 6c56f67ac..230f284ba 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/interfaces/RecipeInterface.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/interfaces/RecipeInterface.kt @@ -2,6 +2,7 @@ package org.xodium.vanillaplus.interfaces import org.bukkit.inventory.Recipe import org.xodium.vanillaplus.VanillaPlus.Companion.instance +import java.util.logging.Logger import kotlin.time.measureTime /** Represents a contract for recipes within the system. */ @@ -12,12 +13,18 @@ internal interface RecipeInterface { */ val recipes: Collection - /** Registers all recipes returned by [recipes] with the server. */ - fun register() { - instance.logger.info( - "Registering: ${this::class.simpleName} | Took ${ - measureTime { recipes.forEach { recipe -> instance.server.addRecipe(recipe) } }.inWholeMilliseconds - }ms", - ) + /** + * Registers all recipes returned by [recipes] with the server. + * @return The time taken to register the recipes in milliseconds. + */ + fun register(): Long = measureTime { recipes.forEach { instance.server.addRecipe(it) } }.inWholeMilliseconds + + /** + * Logs the registration details of a list of recipes. + * @receiver Logger The logger to use for logging. + * @param recipes List of [RecipeInterface] instances to log. + */ + fun Logger.info(recipes: List) { + info("Registered: ${recipes.sumOf { it.recipes.size }} recipes(s) | Took ${recipes.sumOf { it.register() }}ms") } } From 6be81fdb2ab2b476cfd4de1f41b6c0597b09c72d Mon Sep 17 00:00:00 2001 From: Illyrius Date: Fri, 23 Jan 2026 14:41:53 +0100 Subject: [PATCH 64/64] style: remove unnecessary blank line in VanillaPlus.kt Signed-off-by: Illyrius --- src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt index 46e0c5726..f554db910 100644 --- a/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt +++ b/src/main/kotlin/org/xodium/vanillaplus/VanillaPlus.kt @@ -40,6 +40,7 @@ internal class VanillaPlus : JavaPlugin() { ConfigData.reloadCommand.aliases, ) } + instance.server.pluginManager.addPermission(ConfigData.reloadPermission) configData = ConfigData().load("config.json")