diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 56f215cb26e..00000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -patreon: xenondevs diff --git a/.github/ISSUE_TEMPLATE/binary-adapter-request.yml b/.github/ISSUE_TEMPLATE/binary-adapter-request.yml index 4dd688c0fcd..2385ca286e2 100644 --- a/.github/ISSUE_TEMPLATE/binary-adapter-request.yml +++ b/.github/ISSUE_TEMPLATE/binary-adapter-request.yml @@ -1,6 +1,5 @@ name: Binary Adapter Request description: Request a new binary adapter. -title: "[Request - BinaryAdapter] " labels: [ "type: enhancement" ] body: - type: input diff --git a/.github/ISSUE_TEMPLATE/generic-feature-request.yml b/.github/ISSUE_TEMPLATE/generic-feature-request.yml index 95892f38267..09df053e3b6 100644 --- a/.github/ISSUE_TEMPLATE/generic-feature-request.yml +++ b/.github/ISSUE_TEMPLATE/generic-feature-request.yml @@ -1,6 +1,5 @@ name: Generic Feature Request description: Suggest an idea for this project. -title: "[Request - Generic] " labels: [ "type: enhancement" ] body: - type: textarea diff --git a/.github/workflows/dokka.yml b/.github/workflows/dokka.yml index 85eba4b1435..a10ac1ffb6d 100644 --- a/.github/workflows/dokka.yml +++ b/.github/workflows/dokka.yml @@ -34,6 +34,6 @@ jobs: --scan - name: Deploy to Github Pages - uses: JamesIves/github-pages-deploy-action@v4.7.1 + uses: JamesIves/github-pages-deploy-action@v4.7.3 with: folder: build/dokka/htmlMultiModule diff --git a/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt b/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt index c75fb59dc67..8e336a171d7 100644 --- a/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt +++ b/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt @@ -43,7 +43,7 @@ abstract class BuildBundlerJarTask : DefaultTask() { // include dependencies val runtimeArtifacts = nova.configurations.getByName("mojangMappedServerRuntime").incoming.artifacts.artifacts - .mapTo(HashSet()) { (it.id.componentIdentifier as ModuleComponentIdentifier).moduleIdentifier } + .mapNotNullTo(HashSet()) { (it.id.componentIdentifier as? ModuleComponentIdentifier)?.moduleIdentifier } nova.configurations.getByName("novaLoader").incoming.artifacts.artifacts .asSequence() .filter { (it.id.componentIdentifier as ModuleComponentIdentifier).moduleIdentifier !in runtimeArtifacts } diff --git a/gradle.properties b/gradle.properties index 770a84c64cc..67b5323ca74 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version = 0.18-alpha.2 +version = 0.18 kotlin.daemon.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f856d62f60..4b29c3dfe48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,42 +3,43 @@ format.version = "1.1" [versions] bytebase = "0.4.8" -cbf = "0.17" -invui = "2.0.0-alpha.2" +cbf = "0.18" jgrapht = "1.5.2" -kotlin = "2.1.0" -kotlinx-coroutines = "1.9.0" -ktor = "3.0.1" +kotlin = "2.1.20" +kotlinx-coroutines = "1.10.1" +ktor = "3.1.2" paper = "1.21.4-R0.1-SNAPSHOT" -paperweight = "1.7.5" -xenondevs-commons = "1.22" +paperweight = "2.0.0-beta.16" +xenondevs-commons = "1.26" [libraries] -awssdk-s3 = { group = "software.amazon.awssdk", name = "s3", version = "2.29.23" } +awssdk-s3 = { group = "software.amazon.awssdk", name = "s3", version = "2.31.16" } bstats = { group = "xyz.xenondevs.bstats", name = "bstats-bukkit", version = "3.0.1" } bytebase = { group = "xyz.xenondevs.bytebase", name = "ByteBase", version.ref = "bytebase" } bytebase-runtime = { group = "xyz.xenondevs.bytebase", name = "ByteBase-Runtime", version.ref = "bytebase" } -caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version = "3.1.8" } +caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version = "3.2.0" } commons-collections = { group = "xyz.xenondevs.commons", name = "commons-collections", version.ref = "xenondevs-commons" } commons-gson = { group = "xyz.xenondevs.commons", name = "commons-gson", version.ref = "xenondevs-commons" } commons-guava = { group = "xyz.xenondevs.commons", name = "commons-guava", version.ref = "xenondevs-commons" } commons-provider = { group = "xyz.xenondevs.commons", name = "commons-provider", version.ref = "xenondevs-commons" } commons-reflection = { group = "xyz.xenondevs.commons", name = "commons-reflection", version.ref = "xenondevs-commons" } -configurate-yaml = { group = "org.spongepowered", name = "configurate-yaml", version = "4.1.2" } +configurate-yaml = { group = "org.spongepowered", name = "configurate-yaml", version = "4.2.0" } cosmic-binary-format = { group = "xyz.xenondevs.cbf", name = "cosmic-binary-format", version.ref = "cbf" } fuzzywuzzy = { group = "me.xdrop", name = "fuzzywuzzy", version = "1.4.0" } -invui-kotlin = { group = "xyz.xenondevs.invui", name = "invui-kotlin", version.ref = "invui" } +invui-kotlin = { group = "xyz.xenondevs.invui", name = "invui-kotlin", version = "2.0.0-alpha.7" } jgrapht-core = { group = "org.jgrapht", name = "jgrapht-core", version.ref = "jgrapht" } jgrapht-io = { group = "org.jgrapht", name = "jgrapht-io", version.ref = "jgrapht" } jimfs = { group = "com.google.jimfs", name = "jimfs", version = "1.3.0" } joml-primitives = { group = "org.joml", name = "joml-primitives", version = "1.10.0" } -junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version = "5.11.3" } +junit-bom = { module = "org.junit:junit-bom", version = "5.12.1" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } +junit-platformLauncher = { module = "org.junit.platform:junit-platform-launcher" } kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } kotlin-test-junit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-core-jvm = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core-jvm", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-debug = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-debug", version.ref = "kotlinx-coroutines" } -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.7.3" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.8.1" } ktor-client-cio-jvm = { group = "io.ktor", name = "ktor-client-cio-jvm", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-core-jvm = { group = "io.ktor", name = "ktor-client-core-jvm", version.ref = "ktor" } @@ -49,8 +50,8 @@ lz4 = { group = "org.lz4", name = "lz4-java", version = "1.8.0" } minecraft-asset-downloader = { group = "xyz.xenondevs", name = "minecraft-asset-downloader", version = "1.4" } minecraft-model-renderer = { group = "xyz.xenondevs", name = "minecraft-model-renderer", version = "1.3" } paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } -snakeyaml-engine = { group = "org.snakeyaml", name = "snakeyaml-engine", version = "2.8" } -zstd = { group = "com.github.luben", name = "zstd-jni", version = "1.5.6-8" } +snakeyaml-engine = { group = "org.snakeyaml", name = "snakeyaml-engine", version = "2.9" } +zstd = { group = "com.github.luben", name = "zstd-jni", version = "1.5.7-2" } [bundles] cbf = ["cosmic-binary-format"] @@ -58,11 +59,10 @@ jgrapht = ["jgrapht-core", "jgrapht-io"] kotlin = ["kotlin-stdlib", "kotlin-reflect", "kotlinx-coroutines-core-jvm", "kotlinx-coroutines-debug"] ktor = ["ktor-server-core-jvm", "ktor-server-cio-jvm", "ktor-client-core-jvm", "ktor-client-cio-jvm", "ktor-client-content-negotiation", "ktor-serialization-gson-jvm"] minecraft-assets = ["minecraft-asset-downloader", "minecraft-model-renderer"] -test = ["kotlin-test-junit", "junit-jupiter"] xenondevs-commons = ["commons-collections", "commons-gson", "commons-guava", "commons-provider", "commons-reflection"] [plugins] -dokka = { id = "org.jetbrains.dokka", version = "1.9.20" } +dokka = { id = "org.jetbrains.dokka", version = "2.0.0" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a4f0..cea7a793a84 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-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/nova-api/build.gradle.kts b/nova-api/build.gradle.kts index 95a63b131d6..2dc628afaff 100644 --- a/nova-api/build.gradle.kts +++ b/nova-api/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } dependencies { - implementation("org.jetbrains:annotations:26.0.1") + implementation("org.jetbrains:annotations:26.0.2") compileOnly(libs.paper.api) } diff --git a/nova-gradle-plugin/src/main/kotlin/xyz/xenondevs/novagradle/task/AddonJarTask.kt b/nova-gradle-plugin/src/main/kotlin/xyz/xenondevs/novagradle/task/AddonJarTask.kt index 15ce72448e1..ffb8245d67b 100644 --- a/nova-gradle-plugin/src/main/kotlin/xyz/xenondevs/novagradle/task/AddonJarTask.kt +++ b/nova-gradle-plugin/src/main/kotlin/xyz/xenondevs/novagradle/task/AddonJarTask.kt @@ -207,6 +207,35 @@ abstract class AddonJarTask : DefaultTask() { private fun generateBootstrapper(path: Path) { val bootstrapper = ClassNode(Opcodes.ASM9) + + // default bootstrap (empty) + val defaultBootstrap = MethodNode( + Opcodes.ACC_PUBLIC, + "bootstrap", + "(Lio/papermc/paper/plugin/bootstrap/BootstrapContext;)V" + ) { + addLabel() + _return() + } + + // default createPlugin (calls interface default) + val defaultCreatePlugin = MethodNode( + Opcodes.ACC_PUBLIC, + "createPlugin", + "(Lio/papermc/paper/plugin/bootstrap/PluginProviderContext;)Lorg/bukkit/plugin/java/JavaPlugin;" + ) { + addLabel() + aLoad(0) + aLoad(1) + invokeSpecial( + "io/papermc/paper/plugin/bootstrap/PluginBootstrap", + "createPlugin", + "(Lio/papermc/paper/plugin/bootstrap/PluginProviderContext;)Lorg/bukkit/plugin/java/JavaPlugin;", + isInterface = true + ) + areturn() + } + if (path.notExists()) { bootstrapper.apply { version = Opcodes.V21 @@ -215,7 +244,6 @@ abstract class AddonJarTask : DefaultTask() { superName = "java/lang/Object" interfaces = listOf("io/papermc/paper/plugin/bootstrap/PluginBootstrap") methods = mutableListOf( - // MethodNode( Opcodes.ACC_PUBLIC, "", @@ -225,34 +253,6 @@ abstract class AddonJarTask : DefaultTask() { aLoad(0) invokeSpecial("java/lang/Object", "", "()V") _return() - }, - - // bootstrap (empty) - MethodNode( - Opcodes.ACC_PUBLIC, - "bootstrap", - "(Lio/papermc/paper/plugin/bootstrap/BootstrapContext;)V" - ) { - addLabel() - _return() - }, - - // createPlugin (calls interface default) - MethodNode( - Opcodes.ACC_PUBLIC, - "createPlugin", - "(Lio/papermc/paper/plugin/bootstrap/PluginProviderContext;)Lorg/bukkit/plugin/java/JavaPlugin;" - ) { - addLabel() - aLoad(0) - aLoad(1) - invokeSpecial( - "io/papermc/paper/plugin/bootstrap/PluginBootstrap", - "createPlugin", - "(Lio/papermc/paper/plugin/bootstrap/PluginProviderContext;)Lorg/bukkit/plugin/java/JavaPlugin;", - isInterface = true - ) - areturn() } ) } @@ -264,7 +264,9 @@ abstract class AddonJarTask : DefaultTask() { } // inserts AddonBootstrapper.bootstrap(context, getClass().getClassLoader()) at beginning - bootstrapper.methods.first { it.name == "bootstrap" }.instructions.insert(buildInsnList { + val bootstrap = bootstrapper.methods.firstOrNull { it.name == "bootstrap" } + ?: defaultBootstrap.also { bootstrapper.methods.add(it) } + bootstrap.instructions.insert(buildInsnList { addLabel() aLoad(1) aLoad(0) @@ -278,7 +280,9 @@ abstract class AddonJarTask : DefaultTask() { }) // inserts AddonBootstrapper.handleJavaPluginCreated(JavaPlugin, PluginProviderContext, ClassLoader) before return - bootstrapper.methods.first { it.name == "createPlugin" }.insertBeforeEvery(buildInsnList { + val createPlugin = bootstrapper.methods.firstOrNull { it.name == "createPlugin" } + ?: defaultCreatePlugin.also { bootstrapper.methods.add(it) } + createPlugin.insertBeforeEvery(buildInsnList { dup() aLoad(1) // PluginProviderContext aLoad(0) diff --git a/nova-hooks/nova-hook-fastasyncworldedit/build.gradle.kts b/nova-hooks/nova-hook-fastasyncworldedit/build.gradle.kts deleted file mode 100644 index b38757a5329..00000000000 --- a/nova-hooks/nova-hook-fastasyncworldedit/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - alias(libs.plugins.kotlin) - alias(libs.plugins.paperweight) -} - -dependencies { - paperweight.paperDevBundle(libs.versions.paper) - implementation(project(":nova")) - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Libs-Core:2.11.1") { isTransitive = false } - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core:2.9.1") { isTransitive = false } - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Bukkit:2.11.1") { isTransitive = false } -} \ No newline at end of file diff --git a/nova-hooks/nova-hook-fastasyncworldedit/src/main/kotlin/xyz/xenondevs/nova/hook/impl/fastasyncworldedit/FaweHook.kt b/nova-hooks/nova-hook-fastasyncworldedit/src/main/kotlin/xyz/xenondevs/nova/hook/impl/fastasyncworldedit/FaweHook.kt deleted file mode 100644 index 302447e4335..00000000000 --- a/nova-hooks/nova-hook-fastasyncworldedit/src/main/kotlin/xyz/xenondevs/nova/hook/impl/fastasyncworldedit/FaweHook.kt +++ /dev/null @@ -1,73 +0,0 @@ -package xyz.xenondevs.nova.hook.impl.fastasyncworldedit - -import com.sk89q.worldedit.EditSession -import com.sk89q.worldedit.WorldEdit -import com.sk89q.worldedit.bukkit.BukkitAdapter -import com.sk89q.worldedit.event.extent.EditSessionEvent -import com.sk89q.worldedit.extension.input.ParserContext -import com.sk89q.worldedit.extent.AbstractDelegateExtent -import com.sk89q.worldedit.internal.registry.InputParser -import com.sk89q.worldedit.util.eventbus.Subscribe -import com.sk89q.worldedit.world.block.BaseBlock -import com.sk89q.worldedit.world.block.BlockStateHolder -import com.sk89q.worldedit.world.block.BlockTypes -import xyz.xenondevs.nova.integration.Hook -import xyz.xenondevs.nova.registry.NovaRegistries -import xyz.xenondevs.nova.util.contains -import xyz.xenondevs.nova.util.getValueOrThrow -import xyz.xenondevs.nova.world.BlockPos -import xyz.xenondevs.nova.world.format.WorldDataManager -import java.util.stream.Stream - -private val BLOCK_STATE = BlockTypes.BARRIER!!.defaultState - -@Hook(plugins = ["WorldEdit", "FastAsyncWorldEdit"], requireAll = true) -internal object FaweHook { - - init { - val worldEdit = WorldEdit.getInstance() - worldEdit.blockFactory.register(NovaBlockInputParser(worldEdit)) - worldEdit.eventBus.register(this) - } - - @Subscribe - fun handleEditSession(event: EditSessionEvent) { - if (event.stage == EditSession.Stage.BEFORE_CHANGE) { - event.extent = NovaBlockExtent(event) - } - } - -} - -class NovaBlock(val novaId: String) : BaseBlock(BLOCK_STATE) - -internal class NovaBlockInputParser(worldEdit: WorldEdit) : InputParser(worldEdit) { - - override fun getSuggestions(input: String): Stream { - return NovaRegistries.BLOCK.stream() - .filter { it.id.toString().startsWith(input) || it.id.value().startsWith(input) } - .map { it.id.toString() } - } - - override fun parseFromInput(input: String, context: ParserContext): BaseBlock? { - if (input !in NovaRegistries.BLOCK) - return null - - return NovaBlock(input) - } - -} - -internal class NovaBlockExtent(private val event: EditSessionEvent) : AbstractDelegateExtent(event.extent) { - - override fun ?> setBlock(x: Int, y: Int, z: Int, block: T): Boolean { - if (block is NovaBlock) { - val pos = BlockPos(BukkitAdapter.adapt(event.world), x, y, z) - WorldDataManager.setBlockState(pos, NovaRegistries.BLOCK.getValueOrThrow(block.novaId).defaultBlockState) - return true - } - - return super.setBlock(x, y, z, block) - } - -} diff --git a/nova-hooks/nova-hook-itemsadder/src/main/kotlin/xyz/xenondevs/nova/hook/impl/itemsadder/ItemsAdderHook.kt b/nova-hooks/nova-hook-itemsadder/src/main/kotlin/xyz/xenondevs/nova/hook/impl/itemsadder/ItemsAdderHook.kt index 3fefddd5303..1f9c4a3006f 100644 --- a/nova-hooks/nova-hook-itemsadder/src/main/kotlin/xyz/xenondevs/nova/hook/impl/itemsadder/ItemsAdderHook.kt +++ b/nova-hooks/nova-hook-itemsadder/src/main/kotlin/xyz/xenondevs/nova/hook/impl/itemsadder/ItemsAdderHook.kt @@ -9,7 +9,6 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import org.bukkit.Location import org.bukkit.Material -import org.bukkit.NamespacedKey import org.bukkit.block.Block import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.integration.Hook @@ -96,10 +95,6 @@ internal object ItemsAdderHook : CustomItemService { return getItemById(id)?.let { ModelDataTest(it.type, intArrayOf(it.customModelData), it) } } - override fun hasRecipe(key: NamespacedKey): Boolean { - return ItemsAdder.isCustomRecipe(key) - } - override fun getId(item: ItemStack): String? { return CustomStack.byItemStack(item)?.namespacedID } diff --git a/nova-hooks/nova-hook-mmoitems/src/main/kotlin/xyz/xenondevs/nova/hook/impl/mmoitems/MMOItemsHook.kt b/nova-hooks/nova-hook-mmoitems/src/main/kotlin/xyz/xenondevs/nova/hook/impl/mmoitems/MMOItemsHook.kt index d87e7fd05c3..0f984d1b435 100644 --- a/nova-hooks/nova-hook-mmoitems/src/main/kotlin/xyz/xenondevs/nova/hook/impl/mmoitems/MMOItemsHook.kt +++ b/nova-hooks/nova-hook-mmoitems/src/main/kotlin/xyz/xenondevs/nova/hook/impl/mmoitems/MMOItemsHook.kt @@ -95,10 +95,6 @@ internal object MMOItemsHook : CustomItemService { return item.displayName() } - override fun hasRecipe(key: NamespacedKey): Boolean { - return key.namespace == "mmoitems" - } - override fun canBreakBlock(block: Block, tool: ItemStack?): Boolean? { return null } diff --git a/nova-hooks/nova-hook-oraxen/build.gradle.kts b/nova-hooks/nova-hook-nexo/build.gradle.kts similarity index 64% rename from nova-hooks/nova-hook-oraxen/build.gradle.kts rename to nova-hooks/nova-hook-nexo/build.gradle.kts index 2651d63d342..1569bd5120e 100644 --- a/nova-hooks/nova-hook-oraxen/build.gradle.kts +++ b/nova-hooks/nova-hook-nexo/build.gradle.kts @@ -4,12 +4,12 @@ plugins { } repositories { - maven("https://repo.xenondevs.xyz/third-party-releases/") + maven("https://repo.nexomc.com/releases") } dependencies { paperweight.paperDevBundle(libs.versions.paper) implementation(project(":nova")) implementation(project(":nova-api")) - compileOnly("io.th0rgal:oraxen:1.157.2") { isTransitive = false } + compileOnly("com.nexomc:nexo:1.0.0") } \ No newline at end of file diff --git a/nova-hooks/nova-hook-nexo/src/main/kotlin/xyz/xenondevs/nova/hook/impl/nexo/NexoItemsService.kt b/nova-hooks/nova-hook-nexo/src/main/kotlin/xyz/xenondevs/nova/hook/impl/nexo/NexoItemsService.kt new file mode 100644 index 00000000000..c3220cf3703 --- /dev/null +++ b/nova-hooks/nova-hook-nexo/src/main/kotlin/xyz/xenondevs/nova/hook/impl/nexo/NexoItemsService.kt @@ -0,0 +1,115 @@ +package xyz.xenondevs.nova.hook.impl.nexo + +import com.nexomc.nexo.api.NexoBlocks +import com.nexomc.nexo.api.NexoItems +import com.nexomc.nexo.utils.drops.Drop +import net.kyori.adventure.key.Key +import net.kyori.adventure.text.Component +import org.bukkit.Location +import org.bukkit.block.Block +import org.bukkit.inventory.ItemStack +import xyz.xenondevs.nova.integration.Hook +import xyz.xenondevs.nova.integration.customitems.CustomBlockType +import xyz.xenondevs.nova.integration.customitems.CustomItemService +import xyz.xenondevs.nova.integration.customitems.CustomItemType +import xyz.xenondevs.nova.resources.ResourcePath +import xyz.xenondevs.nova.resources.ResourceType +import xyz.xenondevs.nova.world.item.recipe.SingleItemTest +import kotlin.random.Random + +@Hook(plugins = ["Nexo"]) +internal object NexoItemsService : CustomItemService { + + // Note: Furniture is intentionally not supported because it is not a block, + // which causes issues like block placer placing multiple furniture pieces into each other + + private val NO_DROP = Drop(mutableListOf(), false, false, "no_drops_please") + + override fun removeBlock(block: Block, breakEffects: Boolean): Boolean { + if (NexoBlocks.isCustomBlock(block)) { + return NexoBlocks.remove(block.location, overrideDrop = NO_DROP) + } + + return false + } + + override fun placeBlock(item: ItemStack, location: Location, playSound: Boolean): Boolean { + if (NexoBlocks.isCustomBlock(item)) { + NexoBlocks.place(NexoItems.idFromItem(item), location) + return true + } + + return false + } + + override fun getDrops(block: Block, tool: ItemStack?): List? { + val breakable = NexoBlocks.customBlockMechanic(block.location)?.breakable + if (breakable != null) { + val drop = breakable.drop + return drop.loots() + .filter { loot -> drop.canDrop(tool) && Random.nextDouble() > (1 - loot.probability) } + .map { loot -> loot.getItem(1) } + } + + return null + } + + override fun getItemType(item: ItemStack): CustomItemType? { + if (getId(item) != null) + return CustomItemType.NORMAL + + return null + } + + override fun getBlockType(block: Block): CustomBlockType? { + if (NexoBlocks.isCustomBlock(block)) + return CustomBlockType.NORMAL + + return null + } + + override fun getItemById(id: String): ItemStack? { + return NexoItems.itemFromId(id.removePrefix("nexo:"))?.build() + } + + override fun getItemTest(id: String): SingleItemTest? { + val example = getItemById(id) ?: return null + + return object : SingleItemTest { + override val example = example + override fun test(item: ItemStack) = getId(item) == id + } + } + + override fun getId(item: ItemStack): String? { + val id = NexoItems.idFromItem(item) + ?: return null + return "nexo:$id" + } + + override fun getId(block: Block): String? { + val id = NexoBlocks.customBlockMechanic(block.location)?.itemID + ?: return null + return "nexo:$id" + } + + override fun getName(item: ItemStack, locale: String): Component? { + return item.effectiveName() + } + + override fun getName(block: Block, locale: String): Component? { + return NexoItems.itemFromId(getId(block))?.itemName + } + + override fun canBreakBlock(block: Block, tool: ItemStack?): Boolean? { + val breakable = NexoBlocks.customBlockMechanic(block.location)?.breakable + return breakable?.drop?.canDrop(tool) + } + + override fun getBlockItemModelPaths(): Map> { + // Missing API + return emptyMap() + } + + +} \ No newline at end of file diff --git a/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenItemService.kt b/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenItemService.kt deleted file mode 100644 index c1adfc54669..00000000000 --- a/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenItemService.kt +++ /dev/null @@ -1,186 +0,0 @@ -package xyz.xenondevs.nova.hook.impl.oraxen - -import io.th0rgal.oraxen.api.OraxenBlocks -import io.th0rgal.oraxen.api.OraxenFurniture -import io.th0rgal.oraxen.api.OraxenItems -import io.th0rgal.oraxen.mechanics.Mechanic -import io.th0rgal.oraxen.mechanics.MechanicsManager -import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanic -import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic -import io.th0rgal.oraxen.mechanics.provided.gameplay.noteblock.NoteBlockMechanic -import io.th0rgal.oraxen.mechanics.provided.gameplay.stringblock.StringBlockMechanic -import io.th0rgal.oraxen.utils.blocksounds.BlockSounds -import io.th0rgal.oraxen.utils.drops.Drop -import net.kyori.adventure.key.Key -import net.kyori.adventure.text.Component -import org.bukkit.Location -import org.bukkit.NamespacedKey -import org.bukkit.Rotation -import org.bukkit.SoundCategory -import org.bukkit.block.Block -import org.bukkit.block.BlockFace -import org.bukkit.inventory.ItemStack -import xyz.xenondevs.nova.integration.Hook -import xyz.xenondevs.nova.integration.customitems.CustomBlockType -import xyz.xenondevs.nova.integration.customitems.CustomItemService -import xyz.xenondevs.nova.integration.customitems.CustomItemType -import xyz.xenondevs.nova.resources.ResourcePath -import xyz.xenondevs.nova.resources.ResourceType -import xyz.xenondevs.nova.util.item.customModelData -import xyz.xenondevs.nova.world.item.recipe.ModelDataTest -import xyz.xenondevs.nova.world.item.recipe.SingleItemTest -import xyz.xenondevs.nova.world.pos -import kotlin.random.Random -import kotlin.streams.asSequence - -private val Mechanic.drop: Drop? - get() = when (this) { - is BlockMechanic -> drop - is NoteBlockMechanic -> drop - is StringBlockMechanic -> drop - is FurnitureMechanic -> drop - else -> null - } - -private val Mechanic.blockSounds: BlockSounds? - get() = when (this) { - is BlockMechanic -> blockSounds - is NoteBlockMechanic -> blockSounds - is StringBlockMechanic -> blockSounds - is FurnitureMechanic -> blockSounds - else -> null - } - -private val BLOCK_MECHANIC_FACTORIES = listOfNotNull( - MechanicsManager.getMechanicFactory("block"), - MechanicsManager.getMechanicFactory("noteblock"), - MechanicsManager.getMechanicFactory("stringblock"), - MechanicsManager.getMechanicFactory("furniture"), -) - -@Hook(plugins = ["Oraxen"]) -internal object OraxenItemService : CustomItemService { - - override fun removeBlock(block: Block, breakEffects: Boolean): Boolean { - val mechanic = removeAndGetMechanic(block) - ?: return false - - if (breakEffects) { - val blockSounds = mechanic.blockSounds - if (blockSounds != null) { - block.pos.playSound(blockSounds.breakSound, SoundCategory.BLOCKS, blockSounds.breakVolume, blockSounds.breakPitch) - } - } - - return false - } - - private fun removeAndGetMechanic(block: Block): Mechanic? { - val location = block.location - - val oraxenBlock = OraxenBlocks.getOraxenBlock(location) - if (oraxenBlock != null) { - OraxenBlocks.remove(location, null) - return oraxenBlock - } - - val oraxenFurniture = OraxenFurniture.getFurnitureMechanic(block) - if (oraxenFurniture != null) { - OraxenFurniture.remove(location, null) - return oraxenFurniture - } - - return null - } - - override fun getDrops(block: Block, tool: ItemStack?): List? { - val drop = getOraxenDrop(block) ?: return null - - val loots = drop.takeUnless { it.isToolEnough(tool) }?.loots - ?: return emptyList() - - val drops = ArrayList() - loots.forEach { loot -> - repeat(loot.maxAmount) { - if (Random.nextInt(loot.probability) == 0) - drops.add(loot.itemStack) - } - } - - return drops - } - - private fun getOraxenDrop(block: Block): Drop? { - val mechanic = OraxenBlocks.getOraxenBlock(block.location) ?: OraxenFurniture.getFurnitureMechanic(block) - return mechanic?.drop - } - - override fun placeBlock(item: ItemStack, location: Location, playSound: Boolean): Boolean { - val id = getId(item) ?: return false - OraxenBlocks.place(id, location) - OraxenFurniture.place(location, id, Rotation.NONE, BlockFace.NORTH) - return true - } - - override fun getItemType(item: ItemStack): CustomItemType? { - return if (getId(item) != null) CustomItemType.NORMAL else null - } - - override fun getBlockType(block: Block): CustomBlockType? { - return if (OraxenBlocks.isOraxenBlock(block)) CustomBlockType.NORMAL else null - } - - override fun getItemById(id: String): ItemStack? { - return OraxenItems.getItemById(id.removePrefix("oraxen:")).build() - } - - override fun getItemTest(id: String): SingleItemTest? { - return getItemById(id)?.let { ModelDataTest(it.type, intArrayOf(it.customModelData), it) } - } - - override fun getId(item: ItemStack): String? { - return OraxenItems.getIdByItem(item)?.let { "oraxen:$it" } - } - - override fun getId(block: Block): String? { - val name = OraxenBlocks.getOraxenBlock(block.location)?.itemID - ?: OraxenFurniture.getFurnitureMechanic(block)?.itemID - ?: return null - - return "oraxen:$name" - } - - override fun getName(item: ItemStack, locale: String): Component? { - return if (OraxenItems.getIdByItem(item) != null) - item.displayName() - else null - } - - override fun getName(block: Block, locale: String): Component? { - val item = getId(block)?.let(OraxenItemService::getItemById) ?: return null - return item.displayName() - } - - override fun hasRecipe(key: NamespacedKey): Boolean { - return key.namespace == "oraxen" - } - - override fun canBreakBlock(block: Block, tool: ItemStack?): Boolean? { - return getOraxenDrop(block)?.isToolEnough(tool) ?: return null - } - - override fun getBlockItemModelPaths(): Map> { - return OraxenItems.entryStream().asSequence() - .filter { (id, builder) -> id != null && builder.oraxenMeta?.modelName != null } - .filter { (id, _) -> BLOCK_MECHANIC_FACTORIES.any { it.getMechanic(id) != null } } - .associateTo(HashMap()) { (name, builder) -> - val modelName = builder.oraxenMeta.modelName - - val id = Key.key("oraxen", name) - val path = ResourcePath(ResourceType.Model, "oraxen_converted", "oraxen/$modelName") - - id to path - } - } - -} \ No newline at end of file diff --git a/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenUploadService.kt b/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenUploadService.kt deleted file mode 100644 index 18377ff0002..00000000000 --- a/nova-hooks/nova-hook-oraxen/src/main/kotlin/xyz/xenondevs/nova/hook/impl/oraxen/OraxenUploadService.kt +++ /dev/null @@ -1,25 +0,0 @@ -package xyz.xenondevs.nova.hook.impl.oraxen - -import io.th0rgal.oraxen.config.Settings -import io.th0rgal.oraxen.pack.upload.hosts.Polymath -import org.spongepowered.configurate.ConfigurationNode -import xyz.xenondevs.nova.integration.Hook -import xyz.xenondevs.nova.resources.upload.UploadService -import java.nio.file.Path - -@Hook(plugins = ["Oraxen"]) -internal object OraxenUploadService : UploadService { - - override val names = listOf("oraxen") - - override suspend fun upload(file: Path): String { - val polymath = Polymath(Settings.POLYMATH_SERVER.toString()) - if(!polymath.uploadPack(file.toFile())) throw IllegalStateException("Failed to upload pack to polymath!") - return polymath.minecraftPackURL - } - - override fun loadConfig(cfg: ConfigurationNode) = Unit - - override fun disable() = Unit - -} \ No newline at end of file diff --git a/nova-hooks/nova-hook-plotsquared/build.gradle.kts b/nova-hooks/nova-hook-plotsquared/build.gradle.kts index a7cd1c4d059..160e0613e44 100644 --- a/nova-hooks/nova-hook-plotsquared/build.gradle.kts +++ b/nova-hooks/nova-hook-plotsquared/build.gradle.kts @@ -11,6 +11,6 @@ dependencies { paperweight.paperDevBundle(libs.versions.paper) implementation(project(":nova")) implementation(project(":nova-api")) - compileOnly("com.plotsquared:PlotSquared-Core:6.10.5") { isTransitive = false } - compileOnly("com.plotsquared:PlotSquared-Bukkit:6.11.2") { isTransitive = false } + compileOnly("com.intellectualsites.plotsquared:plotsquared-core:7.5.2") + compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit:7.5.2") { isTransitive = false } } \ No newline at end of file diff --git a/nova/build.gradle.kts b/nova/build.gradle.kts index bf4fe45842c..5b58db71a2a 100644 --- a/nova/build.gradle.kts +++ b/nova/build.gradle.kts @@ -36,7 +36,10 @@ dependencies { novaLoader(libs.kotlinx.serialization.json) // test dependencies - testImplementation(libs.bundles.test) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testImplementation(libs.kotlin.test.junit) + testRuntimeOnly(libs.junit.platformLauncher) } // configure java sources location diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/Nova.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/Nova.kt index 93db73d25c3..0d760d9e446 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/Nova.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/Nova.kt @@ -7,6 +7,8 @@ import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.gson.* +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.LoggerContext import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin import xyz.xenondevs.invui.InvUI @@ -42,18 +44,36 @@ internal val HTTP_CLIENT = HttpClient(CIO) { internal var PLUGIN_READY = false private set +private val INCOMPATIBLE_PLUGINS = setOf( + // FAWE replaces LevelChunkSections, preventing Nova from doing block migrations & world gen + // https://github.com/xenondevs/Nova/issues/560 + "FastAsyncWorldEdit" +) + internal object Nova : JavaPlugin(), INova { override fun onEnable() { - if (BOOTSTRAPPER.remainingAddons > 0) - throw IllegalStateException("${BOOTSTRAPPER.remainingAddons} addons did not load.") - - PLUGIN_READY = true - LIFECYCLE_MANAGER = lifecycleManager - - InvUI.getInstance().setPlugin(this) - Languages.getInstance().enableServerSideTranslations(false) - Initializer.registerEvents() + try { + if (BOOTSTRAPPER.remainingAddons > 0) + throw IllegalStateException("${BOOTSTRAPPER.remainingAddons} addons did not load.") + + val incompatibilities = Bukkit.getServer().pluginManager.plugins + .map { it.name } + .filter { it in INCOMPATIBLE_PLUGINS } + if (incompatibilities.isNotEmpty()) + throw Exception("Nova is not compatible with the following plugin(s): ${incompatibilities.joinToString()}") + + PLUGIN_READY = true + LIFECYCLE_MANAGER = lifecycleManager + + InvUI.getInstance().setPlugin(this) + Languages.getInstance().enableServerSideTranslations(false) + Initializer.registerEvents() + } catch (t: Throwable) { + LOGGER.error("", t) + (LogManager.getContext(false) as LoggerContext).stop() // flush log messages + Runtime.getRuntime().halt(-1) // force-quit + } } override fun onDisable() { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/NovaBootstrapper.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/NovaBootstrapper.kt index 39fdc025b6c..39a2f46f631 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/NovaBootstrapper.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/NovaBootstrapper.kt @@ -22,8 +22,6 @@ import xyz.xenondevs.nova.util.data.Version import xyz.xenondevs.nova.util.data.VersionRange import xyz.xenondevs.nova.util.data.useZip import java.nio.file.Path -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.io.path.Path import kotlin.io.path.exists @@ -105,8 +103,8 @@ internal class NovaBootstrapper : PluginBootstrap { Configs.extractDefaultConfig() CBFAdapters.register() Initializer.start() - } catch (e: Exception) { - LOGGER.error("", e) + } catch (t: Throwable) { + LOGGER.error("", t) (LogManager.getContext(false) as LoggerContext).stop() // flush log messages Runtime.getRuntime().halt(-1) // force-quit } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/worldgen/FeatureRegistry.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/worldgen/FeatureRegistry.kt index e87451ca939..ae95749278f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/worldgen/FeatureRegistry.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/worldgen/FeatureRegistry.kt @@ -35,6 +35,13 @@ interface FeatureRegistry : AddonGetter { return key } + @ExperimentalWorldGen + fun feature(name: String, feature: Feature<*>): ResourceKey> { + val key = ResourceKey.create(Registries.FEATURE, ResourceLocation(addon, name)) + Registries.FEATURE[key] = feature + return key + } + @ExperimentalWorldGen fun

placementModifierType(name: String, placementModifierType: PlacementModifierType

): PlacementModifierType

{ val id = ResourceLocation(addon, name) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt index 3213f97beb8..bfe9018ab31 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt @@ -19,8 +19,8 @@ import net.kyori.adventure.text.event.ClickEvent import net.kyori.adventure.text.event.HoverEvent import net.kyori.adventure.text.format.NamedTextColor import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.state.BlockState import org.bukkit.Bukkit -import org.bukkit.block.data.BlockData import org.bukkit.entity.Player import org.joml.Matrix4f import org.joml.Vector3f @@ -48,7 +48,7 @@ import xyz.xenondevs.nova.registry.NovaRegistries.NETWORK_TYPE import xyz.xenondevs.nova.resources.ResourceGeneration import xyz.xenondevs.nova.resources.builder.ResourcePackBuilder import xyz.xenondevs.nova.resources.upload.AutoUploadManager -import xyz.xenondevs.nova.ui.menu.explorer.creative.ItemsWindow +import xyz.xenondevs.nova.ui.menu.explorer.creative.ItemsMenu import xyz.xenondevs.nova.ui.waila.WailaManager import xyz.xenondevs.nova.util.BlockUtils import xyz.xenondevs.nova.util.CUBE_FACES @@ -60,17 +60,20 @@ import xyz.xenondevs.nova.util.item.novaItem import xyz.xenondevs.nova.util.item.takeUnlessEmpty import xyz.xenondevs.nova.util.novaBlock import xyz.xenondevs.nova.util.runAsyncTask +import xyz.xenondevs.nova.util.runTaskLater import xyz.xenondevs.nova.util.unwrap import xyz.xenondevs.nova.util.world.BlockStateSearcher import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.block.NovaBlock +import xyz.xenondevs.nova.world.block.behavior.LeavesBehavior import xyz.xenondevs.nova.world.block.hitbox.HitboxManager import xyz.xenondevs.nova.world.block.state.model.BackingStateBlockModelProvider import xyz.xenondevs.nova.world.block.state.model.BackingStateConfig import xyz.xenondevs.nova.world.block.state.model.DisplayEntityBlockModelData import xyz.xenondevs.nova.world.block.state.model.DisplayEntityBlockModelProvider import xyz.xenondevs.nova.world.block.state.model.ModelLessBlockModelProvider +import xyz.xenondevs.nova.world.block.state.property.DefaultBlockStateProperties import xyz.xenondevs.nova.world.block.tileentity.TileEntity import xyz.xenondevs.nova.world.block.tileentity.network.NetworkDebugger import xyz.xenondevs.nova.world.block.tileentity.network.NetworkManager @@ -117,6 +120,9 @@ internal object NovaCommand : Command() { .then(literal("getBlockData") .requiresPlayer() .executes0(::showBlockData)) + .then(literal("getVanillaBlockData") + .requiresPlayer() + .executes0(::showVanillaBlockData)) .then(literal("getBlockModelData") .requiresPlayer() .executes0(::showBlockModelData)) @@ -157,13 +163,17 @@ internal object NovaCommand : Command() { .then(literal("searchVanillaBlock") .requiresPlayer() .then(argument("block", VanillaBlockArgumentType) - .then(argument("range", IntegerArgumentType.integer(1, 10)) + .then(argument("range", IntegerArgumentType.integer(0, 10)) .executes0(::searchVanillaBlock)))) .then(literal("searchNovaBlock") .requiresPlayer() .then(argument("block", NovaBlockArgumentType) - .then(argument("range", IntegerArgumentType.integer(1, 10)) - .executes0(::searchNovaBlock))))) + .then(argument("range", IntegerArgumentType.integer(0, 10)) + .executes0(::searchNovaBlock)))) + .then(literal("recalculateLeaveProperties") + .requiresPlayer() + .then(argument("range", IntegerArgumentType.integer(0, 20)) + .executes0(::recalculateLeaveProperties)))) .then(literal("items") .requiresPlayer() .requiresPermission("nova.command.items") @@ -413,6 +423,20 @@ internal object NovaCommand : Command() { } } + private fun showVanillaBlockData(ctx: CommandContext) { + val pos = ctx.player.getTargetBlockExact(8)?.location?.pos + if (pos != null) { + val blockState = pos.nmsBlockState + ctx.source.sender.sendMessage(Component.translatable( + "command.nova.show_vanilla_block_state.success", + NamedTextColor.GRAY, + Component.text(blockState.toString(), NamedTextColor.AQUA) + )) + } else { + ctx.source.sender.sendMessage(Component.translatable("command.nova.show_vanilla_block_state.failure", NamedTextColor.RED)) + } + } + private fun showBlockModelData(ctx: CommandContext) { val pos = ctx.player.getTargetBlockExact(8)?.location?.pos if (pos != null) { @@ -422,12 +446,12 @@ internal object NovaCommand : Command() { val message = when (modelProvider.provider) { is ModelLessBlockModelProvider -> { - val info = modelProvider.info as BlockData + val info = modelProvider.info as BlockState Component.translatable( "command.nova.show_block_model_data.model_less", NamedTextColor.GRAY, Component.text(novaBlockState.toString(), NamedTextColor.AQUA), - Component.text(info.asString, NamedTextColor.AQUA) + Component.text(info.toString(), NamedTextColor.AQUA) ) } @@ -874,8 +898,53 @@ internal object NovaCommand : Command() { )) } + private fun recalculateLeaveProperties(ctx: CommandContext) = runBlocking { + val player = ctx.player + val range: Int = ctx["range"] + + // let all leaves tick, which triggers a chain reaction of scheduled ticks that updates all distances + val leaves = HashSet() + val center = player.location.chunkPos + for (xOff in -range..range) { + for (zOff in -range..range) { + val chunkPos = ChunkPos(center.worldUUID, center.x + xOff, center.z + zOff) + WorldDataManager.getOrLoadChunk(chunkPos).forEachNonEmpty { pos, blockState -> + if (blockState.block.hasBehavior() + && blockState[DefaultBlockStateProperties.LEAVES_PERSISTENT] == true + && blockState[DefaultBlockStateProperties.LEAVES_DISTANCE] == 7 + ) { + LeavesBehavior.handleScheduledTick(pos, blockState) + leaves += pos + } + } + } + } + + // assume that all leaves who now have a distance < 7 are part of a tree and not supposed to be persistent + runTaskLater(20) { + var count = 0 + for (pos in leaves) { + val blockState = WorldDataManager.getBlockState(pos) + if (blockState != null + && blockState.block.hasBehavior() + && blockState.getOrThrow(DefaultBlockStateProperties.LEAVES_DISTANCE) < 7 + ) { + count++ + WorldDataManager.setBlockState(pos, blockState.with(DefaultBlockStateProperties.LEAVES_PERSISTENT, false)) + } + } + + ctx.source.sender.sendMessage(Component.translatable( + "command.nova.recalculate_leave_properties.done", + NamedTextColor.GRAY, + Component.text(leaves.size, NamedTextColor.AQUA), + Component.text(count, NamedTextColor.AQUA) + )) + } + } + private fun openItemInventory(ctx: CommandContext) { - ItemsWindow(ctx.player).show() + ItemsMenu(ctx.player).show() } private fun setRenderDistance(ctx: CommandContext) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/config/ConfigExtractor.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/config/ConfigExtractor.kt index 3f686d2116d..9822d26343f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/config/ConfigExtractor.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/config/ConfigExtractor.kt @@ -28,6 +28,7 @@ import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.inputStream import kotlin.io.path.outputStream +import kotlin.jvm.optionals.getOrNull internal class ConfigExtractor(extractedConfigs: MutableProvider>) { @@ -35,19 +36,19 @@ internal class ConfigExtractor(extractedConfigs: MutableProvider loadYaml(inp) } } - private fun loadYaml(inp: InputStream): Node { + private fun loadYaml(inp: InputStream): Node? { val settings = LoadSettings.builder() .setParseComments(true) .build() val reader = StreamReader(settings, inp.reader()) val parser = ParserImpl(settings, reader) val composer = Composer(settings, parser) - return composer.singleNode.get() + return composer.singleNode.getOrNull() } private fun writeYaml(node: Node, comments: Boolean = true): String { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt index 0a6150bb88b..cd1cf62b36c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt @@ -20,6 +20,29 @@ import xyz.xenondevs.cbf.Compound import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockInteract import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockPlace +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_DROPS +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_EXP_DROPS +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_ITEM_STACK +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_POS +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_STATE_NOVA +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_STORAGE_DROPS +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_TYPE +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_TYPE_NOVA +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_TYPE_VANILLA +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.BLOCK_WORLD +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.CLICKED_BLOCK_FACE +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.INTERACTION_HAND +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.INTERACTION_ITEM_STACK +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_DIRECTION +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_ENTITY +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_LOCATION +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_PLAYER +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_TILE_ENTITY +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_UUID +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.SOURCE_WORLD +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.TILE_ENTITY_DATA_NOVA +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.TILE_ENTITY_NOVA +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes.TOOL_ITEM_STACK import xyz.xenondevs.nova.registry.NovaRegistries import xyz.xenondevs.nova.util.BlockFaceUtils import xyz.xenondevs.nova.util.Location @@ -454,6 +477,11 @@ object DefaultContextParamTypes { * * Autofilled by: * - [SOURCE_ENTITY] if player + * + * Autofills: + * - [SOURCE_ENTITY] + * - [CLICKED_BLOCK_FACE] + * - [BLOCK_DROPS] with [BLOCK_POS] with and without [TOOL_ITEM_STACK] */ val SOURCE_PLAYER: ContextParamType = ContextParamType.builder("source_player") @@ -542,15 +570,19 @@ object DefaultContextParamTypes { * - [BlockBreak] * * Autofilled by: - * - [BLOCK_POS] with and without [TOOL_ITEM_STACK] + * - [BLOCK_POS] with and without [TOOL_ITEM_STACK] with and without [SOURCE_PLAYER] * * Autofills: none + * + * @see BLOCK_STORAGE_DROPS + * @see BLOCK_EXP_DROPS */ val BLOCK_DROPS: DefaultingContextParamType = ContextParamType.builder("block_drops") .optionalIn(BlockBreak) - .autofilledBy(::BLOCK_POS, ::TOOL_ITEM_STACK) { pos, tool -> ToolUtils.isCorrectToolForDrops(pos.block, tool) } - .autofilledBy(::BLOCK_POS) { ToolUtils.isCorrectToolForDrops(it.block, null) } + .autofilledBy(::BLOCK_POS, ::TOOL_ITEM_STACK, ::SOURCE_PLAYER) { pos, tool, player -> player.gameMode != GameMode.CREATIVE && ToolUtils.isCorrectToolForDrops(pos.block, tool) } + .autofilledBy(::BLOCK_POS, ::SOURCE_PLAYER) { pos, player -> player.gameMode != GameMode.CREATIVE && ToolUtils.isCorrectToolForDrops(pos.block, null) } + .autofilledBy(::BLOCK_POS) { pos -> ToolUtils.isCorrectToolForDrops(pos.block, null) } .build(false) /** @@ -562,17 +594,41 @@ object DefaultContextParamTypes { * Optional in intentions: * - [BlockBreak] * - * Autofilled by: - * - [SOURCE_PLAYER] + * Autofilled by: none * * Autofills: none + * + * @see BLOCK_DROPS + * @see BLOCK_EXP_DROPS */ val BLOCK_STORAGE_DROPS: DefaultingContextParamType = ContextParamType.builder("block_storage_drops") .optionalIn(BlockBreak) - .autofilledBy(::SOURCE_PLAYER) { it.gameMode != GameMode.CREATIVE } .build(true) + /** + * Whether block exp orbs should be spawned. + * Defaults to `false` + * + * Required in intentions: none + * + * Optional in intentions: + * - [BlockBreak] + * + * Autofilled by: + * - [BLOCK_DROPS] + * + * Autofills: none + * + * @see BLOCK_DROPS + * @see BLOCK_STORAGE_DROPS + */ + val BLOCK_EXP_DROPS: DefaultingContextParamType = + ContextParamType.builder("block_exp_drops") + .optionalIn(BlockBreak) + .autofilledBy(::BLOCK_DROPS) { it } + .build(false) + /** * Whether block place effects should be played. * Defaults to `true`. @@ -630,14 +686,14 @@ object DefaultContextParamTypes { /** * Whether the data of the block should be included for creative-pick block interactions. - * + * * Required in intentions: none - * + * * Optional in intentions: * - [BlockInteract] - * + * * Autofilled by: none - * + * * Autofills: none */ val INCLUDE_DATA: DefaultingContextParamType = diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemService.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemService.kt index 5782ef2bdcc..7d2995b72a6 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemService.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemService.kt @@ -3,7 +3,6 @@ package xyz.xenondevs.nova.integration.customitems import net.kyori.adventure.key.Key import net.kyori.adventure.text.Component import org.bukkit.Location -import org.bukkit.NamespacedKey import org.bukkit.block.Block import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.resources.ResourcePath @@ -77,11 +76,6 @@ interface CustomItemService { */ fun getName(block: Block, locale: String): Component? - /** - * Checks if this [CustomItemService] registered a recipe with that [key] - */ - fun hasRecipe(key: NamespacedKey): Boolean - /** * Checks if the specified [tool] is good enough for the [block] to drop items. * diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemServiceManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemServiceManager.kt index 24ff64d3e02..44287bff2ca 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemServiceManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/integration/customitems/CustomItemServiceManager.kt @@ -3,7 +3,6 @@ package xyz.xenondevs.nova.integration.customitems import net.kyori.adventure.key.Key import net.kyori.adventure.text.Component import org.bukkit.Location -import org.bukkit.NamespacedKey import org.bukkit.block.Block import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.resources.ResourcePath @@ -58,10 +57,6 @@ object CustomItemServiceManager { return services.firstNotNullOfOrNull { it.getName(block, locale) } } - fun hasRecipe(key: NamespacedKey): Boolean { - return services.any { it.hasRecipe(key) } - } - fun canBreakBlock(block: Block, tool: ItemStack?): Boolean? { return services.firstNotNullOfOrNull { it.canBreakBlock(block, tool) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketHandler.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketHandler.kt index c5453ae2d8e..8178bdf3ef6 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketHandler.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketHandler.kt @@ -10,7 +10,6 @@ import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.PacketListener import net.minecraft.network.protocol.Packet import net.minecraft.network.protocol.game.ClientboundBundlePacket -import net.minecraft.network.protocol.login.ServerboundHelloPacket import org.bukkit.entity.Player import xyz.xenondevs.nova.LOGGER import xyz.xenondevs.nova.network.event.PacketEventManager @@ -34,13 +33,9 @@ class PacketHandler internal constructor(val channel: Channel) : ChannelDuplexHa private val outgoingDropQueue = CopyOnWriteArrayList() var player: Player? = null + internal set var loggedIn = false - - constructor(channel: Channel, player: Player) : this(channel) { - this.player = player - this.loggedIn = true - PacketManager.playerHandlers[player.name] = this - } + internal set override fun write(ctx: ChannelHandlerContext?, msg: Any?, promise: ChannelPromise?) { try { @@ -67,19 +62,14 @@ class PacketHandler internal constructor(val channel: Channel) : ChannelDuplexHa override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { try { - if (msg is ServerboundHelloPacket) { - PacketManager.playerHandlers[msg.name] = this - super.channelRead(ctx, msg) + if (shouldDrop(msg, incomingDropQueue)) + return + + if (msg is Packet<*>) { + val packet = callEvent(msg) ?: return + super.channelRead(ctx, packet) } else { - if (shouldDrop(msg, incomingDropQueue)) - return - - if (msg is Packet<*>) { - val packet = callEvent(msg) ?: return - super.channelRead(ctx, packet) - } else { - super.channelRead(ctx, msg) - } + super.channelRead(ctx, msg) } } catch (t: Throwable) { LOGGER.error("An exception occurred while handling a serverbound packet.", t) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketManager.kt index 0fe9a8733f4..1cc17c3c45a 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/network/PacketManager.kt @@ -2,44 +2,37 @@ package xyz.xenondevs.nova.network import io.netty.channel.Channel import io.netty.channel.ChannelFuture +import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInboundHandlerAdapter -import io.netty.channel.ChannelInitializer import net.kyori.adventure.text.Component -import net.minecraft.network.Connection import net.minecraft.network.FriendlyByteBuf -import net.minecraft.server.network.ServerCommonPacketListenerImpl import net.minecraft.server.network.ServerConnectionListener -import org.bukkit.Bukkit +import net.minecraft.server.network.ServerLoginPacketListenerImpl import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerLoginEvent -import org.bukkit.event.player.PlayerLoginEvent.Result import org.bukkit.event.player.PlayerQuitEvent -import xyz.xenondevs.nova.initialize.DisableFun import xyz.xenondevs.nova.initialize.InitFun import xyz.xenondevs.nova.initialize.InternalInit import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.util.MINECRAFT_SERVER import xyz.xenondevs.nova.util.registerEvents -import xyz.xenondevs.nova.util.serverPlayer import java.lang.invoke.MethodHandles +import java.util.* import net.minecraft.world.entity.player.Player as MojangPlayer private val SERVER_CONNECTION_LISTENER_CHANNELS_GETTER = MethodHandles .privateLookupIn(ServerConnectionListener::class.java, MethodHandles.lookup()) .findGetter(ServerConnectionListener::class.java, "channels", List::class.java) -private val SERVER_COMMON_PACKET_LISTENER_IMPL_CONNECTION_GETTER = MethodHandles - .privateLookupIn(ServerCommonPacketListenerImpl::class.java, MethodHandles.lookup()) - .findGetter(ServerCommonPacketListenerImpl::class.java, "connection", Connection::class.java) val Player.packetHandler: PacketHandler? - get() = PacketManager.playerHandlers[name] + get() = PacketManager.handlers[this] val MojangPlayer.packetHandler: PacketHandler? - get() = PacketManager.playerHandlers[scoreboardName] + get() = PacketManager.handlers[bukkitEntity as Player] fun Player.send(vararg bufs: FriendlyByteBuf, retain: Boolean = true, flush: Boolean = true) { val packetHandler = packetHandler ?: return @@ -51,105 +44,80 @@ fun Player.send(vararg bufs: FriendlyByteBuf, retain: Boolean = true, flush: Boo if (flush) packetHandler.channel.flush() } +private const val INIT_HANDLER_NAME = "nova_init" +private const val PACKET_HANDLER_NAME = "nova_packet_handler" + @InternalInit(stage = InternalInitStage.POST_WORLD) internal object PacketManager : Listener { - private lateinit var serverChannel: Channel - private val connectionsList = MINECRAFT_SERVER.connection.connections - - val playerHandlers = HashMap() + val handlers = WeakHashMap() + @Suppress("UNCHECKED_CAST") @InitFun private fun init() { registerEvents() - registerHandlers() - } - - @DisableFun - private fun disable() { - Bukkit.getOnlinePlayers().forEach(::unregisterHandler) - if (::serverChannel.isInitialized) { - serverChannel.eventLoop().submit { - val pipeline = serverChannel.pipeline() - pipeline.context("nova_pipeline_adapter")?.handler()?.run(pipeline::remove) - } - } - } - - @Suppress("UNCHECKED_CAST") - private fun registerHandlers() { val channels = SERVER_CONNECTION_LISTENER_CHANNELS_GETTER.invoke(MINECRAFT_SERVER.connection) as List - serverChannel = channels.first().channel() - + val serverChannel = channels.first().channel() val pipeline = serverChannel.pipeline() - pipeline.context("nova_pipeline_adapter")?.handler()?.run(pipeline::remove) - pipeline.addFirst("nova_pipeline_adapter", PipelineAdapter) - - Bukkit.getOnlinePlayers().forEach { unregisterHandler(it); registerHandler(it) } + pipeline.addFirst("nova_pipeline_adapter", NovaServerChannelBootstrap) } @EventHandler private fun handleLogin(event: PlayerLoginEvent) { - val handler = playerHandlers[event.player.name] - if (handler == null) { - event.disallow(Result.KICK_OTHER, Component.text("[Nova] Something went wrong")) - return + var deny = true + + // find corresponding packet handler and set player instance + val player = event.player + for (connection in MINECRAFT_SERVER.connection.connections) { + val listener = connection.packetListener + if (listener is ServerLoginPacketListenerImpl && listener.authenticatedProfile?.name == player.name) { + val handler = connection.channel.pipeline().get(PACKET_HANDLER_NAME) as PacketHandler + handler.player = player + handlers[player] = handler + + deny = false + } + } + + if (deny) { + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, Component.text("[Nova] Something went wrong")) } - handler.player = event.player } @EventHandler private fun handleJoin(event: PlayerJoinEvent) { - val handler = playerHandlers[event.player.name]!! + val handler = handlers[event.player]!! handler.loggedIn = true } @EventHandler private fun handleQuit(event: PlayerQuitEvent) { - playerHandlers -= event.player.name + handlers -= event.player } - object PipelineAdapter : ChannelInboundHandlerAdapter() { + private object NovaServerChannelBootstrap : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is Channel) - msg.pipeline().addFirst("nova_pre_init_handler", PreInitHandler) + msg.pipeline().addFirst(INIT_HANDLER_NAME, NovaChannelInitializer) super.channelRead(ctx, msg) } } - object PreInitHandler : ChannelInitializer() { + @Sharable + private object NovaChannelInitializer : ChannelInboundHandlerAdapter() { - override fun initChannel(channel: Channel) { - channel.pipeline().addLast("nova_init_handler", NovaInitHandler) - } - - } - - object NovaInitHandler : ChannelInitializer() { + // uses ChannelInboundHandlerAdapter#channelActive instead of ChannelInitializer#initChannel + // because Minecraft's handlers are not registered at that point - override fun initChannel(channel: Channel) { - synchronized(connectionsList) { - channel.eventLoop().submit { - channel.pipeline().addBefore("packet_handler", "nova_packet_handler", PacketHandler(channel)) - } - } + override fun channelActive(ctx: ChannelHandlerContext) { + ctx.fireChannelActive() + ctx.pipeline().addBefore("packet_handler", PACKET_HANDLER_NAME, PacketHandler(ctx.channel())) + ctx.pipeline().remove(this) } } - private fun registerHandler(player: Player) { - val connection = SERVER_COMMON_PACKET_LISTENER_IMPL_CONNECTION_GETTER.invoke(player.serverPlayer.connection) as Connection - val channel = connection.channel - channel.pipeline().addBefore("packet_handler", "nova_packet_handler", PacketHandler(channel, player)) - } - - private fun unregisterHandler(player: Player) { - val connection = SERVER_COMMON_PACKET_LISTENER_IMPL_CONNECTION_GETTER.invoke(player.serverPlayer.connection) as Connection - val pipeline = connection.channel.pipeline() - pipeline.context("nova_packet_handler")?.handler()?.run(pipeline::remove) - } - } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/network/event/PacketEventManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/network/event/PacketEventManager.kt index d702054c2cb..c0948eb4956 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/network/event/PacketEventManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/network/event/PacketEventManager.kt @@ -6,6 +6,7 @@ import net.minecraft.network.protocol.Packet import org.bukkit.entity.Player import org.bukkit.event.EventPriority import xyz.xenondevs.commons.collections.removeIf +import xyz.xenondevs.commons.reflection.toMethodHandle import xyz.xenondevs.nova.network.event.clientbound.ClientboundActionBarPacketEvent import xyz.xenondevs.nova.network.event.clientbound.ClientboundBlockDestructionPacketEvent import xyz.xenondevs.nova.network.event.clientbound.ClientboundBlockEventPacketEvent @@ -37,13 +38,13 @@ import xyz.xenondevs.nova.network.event.serverbound.ServerboundPlayerActionPacke import xyz.xenondevs.nova.network.event.serverbound.ServerboundSelectBundleItemPacketEvent import xyz.xenondevs.nova.network.event.serverbound.ServerboundSetCreativeModeSlotPacketEvent import xyz.xenondevs.nova.network.event.serverbound.ServerboundUseItemPacketEvent -import java.lang.reflect.Method +import java.lang.invoke.MethodHandle import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlin.reflect.KClass import net.minecraft.network.PacketListener as MojangPacketListener -private data class Listener(val instance: Any, val method: Method, val priority: EventPriority, val ignoreIfCancelled: Boolean) +private data class Listener(val handle: MethodHandle, val priority: EventPriority, val ignoreIfCancelled: Boolean) object PacketEventManager { @@ -121,10 +122,10 @@ object PacketEventManager { } private fun callEvent(event: PacketEvent<*>) { - listeners[event::class]?.forEach { (instance, method, _, ignoreIfCancelled) -> + listeners[event::class]?.forEach { (handle, _, ignoreIfCancelled) -> if (!ignoreIfCancelled || !event.isCancelled) { try { - method.invoke(instance, event) + handle.invoke(event) } catch (t: Throwable) { t.printStackTrace() } @@ -145,7 +146,7 @@ object PacketEventManager { val priority = method.getAnnotation(PacketHandler::class.java).priority val ignoreIfCancelled = method.getAnnotation(PacketHandler::class.java).ignoreIfCancelled - val listener = Listener(listener, method, priority, ignoreIfCancelled) + val listener = Listener(method.toMethodHandle(listener), priority, ignoreIfCancelled) instanceListeners += listener val list = listeners[param]?.let(::ArrayList) ?: ArrayList() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/Patcher.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/Patcher.kt index 1c6d2122e89..0f11d26697e 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/Patcher.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/Patcher.kt @@ -28,6 +28,7 @@ import xyz.xenondevs.nova.patch.impl.item.ItemStackDataComponentsPatch import xyz.xenondevs.nova.patch.impl.item.RemainingItemPatches import xyz.xenondevs.nova.patch.impl.item.RepairPatches import xyz.xenondevs.nova.patch.impl.item.ToolPatches +import xyz.xenondevs.nova.patch.impl.misc.DontCloseAddonClassLoadersPatch import xyz.xenondevs.nova.patch.impl.misc.EventPreventionPatch import xyz.xenondevs.nova.patch.impl.misc.FakePlayerLastHurtPatch import xyz.xenondevs.nova.patch.impl.playerlist.BroadcastPacketPatch @@ -57,7 +58,8 @@ internal object Patcher { BroadcastPacketPatch, EventPreventionPatch, ArmorEquipEventPatch, BossBarOriginPatch, FakePlayerLastHurtPatch, BlockBehaviorPatches, ChunkSchedulingPatch, DisableBackingStateLogicPatch, ItemStackDataComponentsPatch, EnchantmentPatches, RepairPatches, BlockMigrationPatches, - TripwireLogicPatch, FluidFlowPatch, RegistryEventsPatch, DyeablePatches, EarlyBlockPlaceEventPatch + TripwireLogicPatch, FluidFlowPatch, RegistryEventsPatch, DyeablePatches, EarlyBlockPlaceEventPatch, + DontCloseAddonClassLoadersPatch ).filter(Transformer::shouldTransform).toSet() } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/chunk/ChunkSchedulingPatch.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/chunk/ChunkSchedulingPatch.kt index 26f67c921a7..03f6b94112c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/chunk/ChunkSchedulingPatch.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/chunk/ChunkSchedulingPatch.kt @@ -1,20 +1,22 @@ package xyz.xenondevs.nova.patch.impl.chunk -import ca.spottedleaf.moonrise.common.util.ChunkSystem +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask import kotlinx.coroutines.runBlocking import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.chunk.LevelChunk +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodInsnNode import xyz.xenondevs.bytebase.asm.buildInsnList import xyz.xenondevs.bytebase.jvm.VirtualClassPath +import xyz.xenondevs.bytebase.util.insertBeforeEvery import xyz.xenondevs.nova.patch.MultiTransformer import xyz.xenondevs.nova.util.reflection.ReflectionUtils import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.format.WorldDataManager import kotlin.reflect.KFunction - private val CHUNK_DATA_LOAD_TASK = Class.forName("ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkLoadTask\$ChunkDataLoadTask") private val CHUNK_DATA_LOAD_TASK_RUN_OFF_MAIN = ReflectionUtils.getMethod( CHUNK_DATA_LOAD_TASK, @@ -26,7 +28,7 @@ private val GENERIC_DATA_LOAD_TASK_WORLD = ReflectionUtils.getField(GenericDataL private val GENERIC_DATA_LOAD_TASK_CHUNK_X = ReflectionUtils.getField(GenericDataLoadTask::class, "chunkX") private val GENERIC_DATA_LOAD_TASK_CHUNK_Z = ReflectionUtils.getField(GenericDataLoadTask::class, "chunkZ") -internal object ChunkSchedulingPatch : MultiTransformer(CHUNK_DATA_LOAD_TASK.kotlin, ChunkSystem::class) { +internal object ChunkSchedulingPatch : MultiTransformer(CHUNK_DATA_LOAD_TASK.kotlin, NewChunkHolder::class) { override fun transform() { VirtualClassPath[CHUNK_DATA_LOAD_TASK_RUN_OFF_MAIN].instructions.insert(buildInsnList { @@ -40,24 +42,18 @@ internal object ChunkSchedulingPatch : MultiTransformer(CHUNK_DATA_LOAD_TASK.kot invokeStatic(::loadChunkBlocking) }) - fun insertEnableTicking(fn: KFunction<*>) = - VirtualClassPath[fn].instructions.insert(buildInsnList { - addLabel() - aLoad(0) - invokeStatic(::enableChunkTicking) - }) - - fun insertDisableTicking(fn: KFunction<*>) = - VirtualClassPath[fn].instructions.insert(buildInsnList { - addLabel() - aLoad(0) - invokeStatic(::disableChunkTicking) - }) + fun insertBeforePlatformHooks(platformHooksFn: String, fn: KFunction<*>) = + VirtualClassPath[NewChunkHolder::handleFullStatusChange].insertBeforeEvery(buildInsnList { + swap() // ... chunk, vanillaChunkHolder -> ... vanillaChunkHolder, chunk + dup() // ... vanillaChunkHolder, chunk -> ... vanillaChunkHolder, chunk, chunk + invokeStatic(fn) // ... vanillaChunkHolder, chunk, chunk -> ... vanillaChunkHolder, chunk + swap() // ... vanillaChunkHolder, chunk -> ... chunk, vanillaChunkHolder + }) { it.opcode == Opcodes.INVOKEINTERFACE && (it as MethodInsnNode).name == platformHooksFn } - insertEnableTicking(ChunkSystem::onChunkEntityTicking) - insertEnableTicking(ChunkSystem::onChunkTicking) - insertDisableTicking(ChunkSystem::onChunkNotTicking) - insertDisableTicking(ChunkSystem::onChunkNotEntityTicking) + insertBeforePlatformHooks("onChunkTicking", ::enableChunkTicking) + insertBeforePlatformHooks("onChunkEntityTicking", ::enableChunkTicking) + insertBeforePlatformHooks("onChunkNotTicking", ::disableChunkTicking) + insertBeforePlatformHooks("onChunkNotEntityTicking", ::disableChunkTicking) } @JvmStatic diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/EnchantmentPatches.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/EnchantmentPatches.kt index a50af978062..0578150c94d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/EnchantmentPatches.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/EnchantmentPatches.kt @@ -1,7 +1,6 @@ package xyz.xenondevs.nova.patch.impl.item import net.minecraft.core.Holder -import net.minecraft.core.component.DataComponents import net.minecraft.world.item.ItemStack import net.minecraft.world.item.enchantment.Enchantment import net.minecraft.world.item.enchantment.EnchantmentHelper @@ -24,7 +23,6 @@ internal object EnchantmentPatches : MultiTransformer(Enchantment::class, Enchan VirtualClassPath[Enchantment::getMinCost].delegateStatic(::getMinCost) VirtualClassPath[Enchantment::getMaxCost].delegateStatic(::getMaxCost) VirtualClassPath[Enchantment::areCompatible].delegateStatic(::areCompatible) - VirtualClassPath[ItemStack::isEnchantable].delegateStatic(::isEnchantable) } @JvmStatic @@ -100,18 +98,4 @@ internal object EnchantmentPatches : MultiTransformer(Enchantment::class, Enchan return firstCompatSecond && secondCompatFirst } - @JvmStatic - fun isEnchantable(itemStack: ItemStack): Boolean { - val enchantments = itemStack.get(DataComponents.ENCHANTMENTS) - if (enchantments == null || !enchantments.isEmpty) - return false - - val novaItem = itemStack.novaItem - if (novaItem != null) { - return novaItem.hasBehavior() - } else { - return itemStack.isEnchantable - } - } - } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/RemainingItemPatches.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/RemainingItemPatches.kt index 6a7e0099828..3b9c2473c00 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/RemainingItemPatches.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/RemainingItemPatches.kt @@ -63,18 +63,30 @@ internal object RemainingItemPatches : MultiTransformer( } /** - * Changes `blockEntity.items.set(1, item.getCraftingRemainder());` to - * `blockEntity.items.set(1, RemainingItemPatches.getRemainingItemStack(itemStack));` + * Changes + * ```java + * Item item = itemStack.getItem(); + * ... + * furnace.items.set(1, item.getCraftingRemainder()); + * ``` + * to + * ```java + * ItemStack remainder = RemainingItemStackPatches.getCraftingRemainder(itemStack); + * ... + * furnace.items.set(1, remainder); + * ``` */ private fun patchAbstractFurnaceBlockEntityServerTick() { val methodNode = VirtualClassPath[AbstractFurnaceBlockEntity::serverTick] methodNode.localVariables.clear() + + methodNode.replaceFirst( + 0, 0, + buildInsnList { invokeStatic(::getRemainingItemStack) } + ) { it.opcode == Opcodes.INVOKEVIRTUAL && (it as MethodInsnNode).calls(MojangStack::getItem) } methodNode.replaceFirst( - 1, 0, // drop aLoad for item - buildInsnList { - aLoad(6) // ItemStack - invokeStatic(::getRemainingItemStack) - } + 0, 0, + buildInsnList { } // no changes, what was previously item is now already the crafting remainder item stack ) { it.opcode == Opcodes.INVOKEVIRTUAL && (it as MethodInsnNode).calls(Item::getCraftingRemainder) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/ToolPatches.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/ToolPatches.kt index ace4f9d4d5d..768e76ccb20 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/ToolPatches.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/item/ToolPatches.kt @@ -1,31 +1,39 @@ package xyz.xenondevs.nova.patch.impl.item -import net.minecraft.world.item.SwordItem +import net.minecraft.stats.Stats +import net.minecraft.tags.ItemTags +import net.minecraft.tags.TagKey +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.state.BlockState import org.bukkit.craftbukkit.block.CraftBlock import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.TypeInsnNode -import xyz.xenondevs.bytebase.asm.buildInsnList +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.MethodInsnNode import xyz.xenondevs.bytebase.jvm.VirtualClassPath -import xyz.xenondevs.bytebase.util.isClass -import xyz.xenondevs.bytebase.util.replaceFirst +import xyz.xenondevs.bytebase.util.calls +import xyz.xenondevs.bytebase.util.gets +import xyz.xenondevs.bytebase.util.replaceEvery import xyz.xenondevs.nova.patch.MultiTransformer import xyz.xenondevs.nova.util.item.ToolUtils import xyz.xenondevs.nova.util.item.novaItem import xyz.xenondevs.nova.util.item.takeUnlessEmpty import xyz.xenondevs.nova.util.reflection.ReflectionRegistry +import xyz.xenondevs.nova.util.reflection.ReflectionUtils +import xyz.xenondevs.nova.world.item.behavior.Damageable import xyz.xenondevs.nova.world.item.behavior.Tool -import xyz.xenondevs.nova.world.item.tool.ToolCategory -import xyz.xenondevs.nova.world.item.tool.VanillaToolCategory -import net.minecraft.world.entity.player.Player as MojangPlayer -import net.minecraft.world.item.ItemStack as MojangStack + +private val ITEM_STACK_IS_TAG = ReflectionUtils.getMethod(ItemStack::class, "is", TagKey::class) @Suppress("unused") -internal object ToolPatches : MultiTransformer(CraftBlock::class, MojangPlayer::class) { +internal object ToolPatches : MultiTransformer(CraftBlock::class, Player::class, ItemStack::class) { override fun transform() { transformCraftBlockIsPreferredTool() transformPlayerAttack() + transformItemStackHurtEnemy() } /** @@ -42,7 +50,7 @@ internal object ToolPatches : MultiTransformer(CraftBlock::class, MojangPlayer:: } @JvmStatic - fun isPreferredTool(block: CraftBlock, blockState: BlockState, tool: MojangStack): Boolean { + fun isPreferredTool(block: CraftBlock, blockState: BlockState, tool: ItemStack): Boolean { return !blockState.requiresCorrectToolForDrops() || ToolUtils.isCorrectToolForDrops(block, tool.asBukkitMirror().takeUnlessEmpty()) } @@ -50,21 +58,65 @@ internal object ToolPatches : MultiTransformer(CraftBlock::class, MojangPlayer:: * Patches the Player#attack method to use properly perform sweep attacks. */ private fun transformPlayerAttack() { - VirtualClassPath[MojangPlayer::attack] - .replaceFirst(1, 0, buildInsnList { - invokeStatic(::canDoSweepAttack) - }) { it.opcode == Opcodes.INSTANCEOF && (it as TypeInsnNode).isClass(SwordItem::class) } + VirtualClassPath[Player::attack].replaceEvery( + 0, 1, + { invokeStatic(::canDoSweepAttack) }, + { + it.opcode == Opcodes.GETSTATIC && (it as FieldInsnNode).gets(ItemTags::SWORDS) + && it.next.opcode == Opcodes.INVOKEVIRTUAL && (it.next as MethodInsnNode).calls(ITEM_STACK_IS_TAG) + } + ) } @JvmStatic - fun canDoSweepAttack(itemStack: MojangStack): Boolean { + fun canDoSweepAttack(itemStack: ItemStack): Boolean { + val novaItem = itemStack.novaItem + + if (novaItem != null) { + return novaItem.getBehaviorOrNull()?.canSweepAttack == true + } else { + return itemStack.`is`(ItemTags.SWORDS) + } + } + + /** + * Transforms ItemStack#hurtEnemy and ItemStack#postHurtEnemy to implement item damage on entity attack. + */ + private fun transformItemStackHurtEnemy() { + // hurtEnemy needs to return true, otherwise postHurtEnemy is not called + VirtualClassPath[ItemStack::hurtEnemy].delegateStatic(::hurtEnemy) + VirtualClassPath[ItemStack::postHurtEnemy].delegateStatic(::postHurtEnemy) + } + + @JvmStatic + fun hurtEnemy(itemStack: ItemStack, enemy: LivingEntity, attacker: LivingEntity): Boolean { + val novaItem = itemStack.novaItem + val isWeapon = if (novaItem != null) { + val damageable = novaItem.getBehaviorOrNull() + ?: return false + damageable.itemDamageOnAttackEntity > 0 + } else { + itemStack.item.hurtEnemy(itemStack, enemy, attacker) + } + + if (isWeapon && attacker is Player) { + attacker.awardStat(Stats.ITEM_USED.get(itemStack.item)) + } + + return isWeapon + } + + @JvmStatic + fun postHurtEnemy(itemStack: ItemStack, enemy: LivingEntity, attacker: LivingEntity) { val novaItem = itemStack.novaItem + val damage: Int if (novaItem != null) { - return novaItem.getBehaviorOrNull(Tool::class)?.canSweepAttack ?: false + val damageable = novaItem.getBehaviorOrNull() ?: return + damage = damageable.itemDamageOnAttackEntity + itemStack.hurtAndBreak(damage, attacker, EquipmentSlot.MAINHAND) } else { - return ToolCategory.ofItem(itemStack.asBukkitMirror()) - .any { it is VanillaToolCategory && it.canSweepAttack } + itemStack.item.postHurtEnemy(itemStack, enemy, attacker) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/misc/DontCloseAddonClassLoadersPatch.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/misc/DontCloseAddonClassLoadersPatch.kt new file mode 100644 index 00000000000..c9582c16580 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/misc/DontCloseAddonClassLoadersPatch.kt @@ -0,0 +1,42 @@ +@file:Suppress("UnstableApiUsage") + +package xyz.xenondevs.nova.patch.impl.misc + +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader +import org.bukkit.plugin.Plugin +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.TypeInsnNode +import xyz.xenondevs.bytebase.jvm.VirtualClassPath +import xyz.xenondevs.bytebase.util.isClass +import xyz.xenondevs.bytebase.util.replaceEvery +import xyz.xenondevs.nova.addon.AddonBootstrapper +import xyz.xenondevs.nova.patch.MultiTransformer +import xyz.xenondevs.nova.util.reflection.ReflectionUtils + +private val PAPER_PLUGIN_INSTANCE_MANAGER = ReflectionUtils.getClass("io.papermc.paper.plugin.manager.PaperPluginInstanceManager").kotlin +private val DISABLE_PLUGIN = ReflectionUtils.getMethod(PAPER_PLUGIN_INSTANCE_MANAGER, "disablePlugin", Plugin::class) + +/** + * Prevents the plugin class loaders from addons or Nova to be closed. + * This is necessary as Nova may call into addons during its disable (which is after the addon's class + * loader has been closed), which may require class loading. + */ +internal object DontCloseAddonClassLoadersPatch : MultiTransformer(PAPER_PLUGIN_INSTANCE_MANAGER) { + + override fun transform() { + VirtualClassPath[DISABLE_PLUGIN].replaceEvery( + 0, 0, + { invokeStatic(::shouldClosePluginClassLoader) } + ) { it.opcode == Opcodes.INSTANCEOF && (it as TypeInsnNode).isClass(ConfiguredPluginClassLoader::class) } + } + + @JvmStatic + fun shouldClosePluginClassLoader(loader: ClassLoader): Boolean { + if (loader !is ConfiguredPluginClassLoader) + return false + + val plugin = loader.plugin + return plugin == null || (plugin.name != "Nova" && !AddonBootstrapper.addons.any { it.plugin == plugin }) + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/registry/RegistryEventsPatch.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/registry/RegistryEventsPatch.kt index 4e875fb21ef..3b6f48f3664 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/registry/RegistryEventsPatch.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/registry/RegistryEventsPatch.kt @@ -123,7 +123,7 @@ internal object RegistryEventsPatch : MultiTransformer(BuiltInRegistries::class, fun handlePostFreeze2(loaders: List, lookup: RegistryOps.RegistryInfoLookup) { for (loader in loaders) { val registry = REGISTRY_DATA_LOADER_LOADER_GET_REGISTRY(loader) as WritableRegistry<*> - handlePreFreeze(registry, lookup) + handlePostFreeze(registry, lookup) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/worldgen/NovaRuleTestPatch.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/worldgen/NovaRuleTestPatch.kt index fa326b5f847..2c2aaae8eb9 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/worldgen/NovaRuleTestPatch.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/worldgen/NovaRuleTestPatch.kt @@ -13,7 +13,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.PosRuleTest import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorRule import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor import net.minecraft.world.level.levelgen.structure.templatesystem.RuleTest -import org.objectweb.asm.Opcodes.INVOKESTATIC +import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.LabelNode @@ -23,13 +23,11 @@ import xyz.xenondevs.bytebase.jvm.VirtualClassPath import xyz.xenondevs.bytebase.util.calls import xyz.xenondevs.bytebase.util.next import xyz.xenondevs.bytebase.util.previousLabel +import xyz.xenondevs.bytebase.util.replaceEvery import xyz.xenondevs.bytebase.util.replaceFirst -import xyz.xenondevs.commons.collections.findNthOfType import xyz.xenondevs.nova.patch.MultiTransformer import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.BLOCK_GETTER_GET_BLOCK_STATE_METHOD import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.FEATURE_PLACE_CONTEXT_RANDOM_METHOD -import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.ORE_FEATURE_CAN_PLACE_ORE_METHOD -import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.ORE_FEATURE_DO_PLACE_METHOD import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.PROCESSOR_RULE_INPUT_PREDICATE_FIELD import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.PROCESSOR_RULE_LOC_PREDICATE_FIELD import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.PROCESSOR_RULE_POS_PREDICATE_FIELD @@ -41,12 +39,26 @@ import xyz.xenondevs.nova.util.reflection.ReflectionRegistry.TARGET_BLOCK_STATE_ import xyz.xenondevs.nova.util.reflection.ReflectionUtils import xyz.xenondevs.nova.world.generation.ExperimentalWorldGen import xyz.xenondevs.nova.world.generation.ruletest.NovaRuleTest +import java.util.function.Function + +private val ORE_FEATURE_DO_PLACE_METHOD = ReflectionUtils.getMethod( + OreFeature::class, + "doPlace", + WorldGenLevel::class, // level + RandomSource::class, // random + OreConfiguration::class, // config + Double::class, Double::class, // minX, maxX + Double::class, Double::class, // minZ, maxZ + Double::class, Double::class, // minY, maxY + Int::class, Int::class, Int::class, // x, y, z + Int::class, Int::class // width, height +) /** * This patch allows [NovaRuleTest]s to be used in [OreFeature]s, [ReplaceBlockFeature]s and structure [ProcessorRule]s */ @OptIn(ExperimentalWorldGen::class) -internal object NovaRuleTestPatch : MultiTransformer(setOf(OreFeature::class, ReplaceBlockFeature::class, RuleProcessor::class), computeFrames = true) { +internal object NovaRuleTestPatch : MultiTransformer(setOf(OreFeature::class, ReplaceBlockFeature::class, RuleProcessor::class)) { override fun transform() { transformOreFeature() @@ -55,30 +67,30 @@ internal object NovaRuleTestPatch : MultiTransformer(setOf(OreFeature::class, Re } private fun transformOreFeature() { - val placeMethod = VirtualClassPath[ORE_FEATURE_DO_PLACE_METHOD] - placeMethod.localVariables?.clear() - val canPlaceCall = placeMethod.instructions.find { it.opcode == INVOKESTATIC && (it as MethodInsnNode).calls(ORE_FEATURE_CAN_PLACE_ORE_METHOD) } as MethodInsnNode - val cantPlaceLabel = (canPlaceCall.next as JumpInsnNode).label - val prevLabel = canPlaceCall.previousLabel() - placeMethod.instructions.insertBefore(prevLabel, buildInsnList { - addLabel() - aLoad(56) - aLoad(2) - aLoad(23) - aLoad(1) - aLoad(58) - invokeStatic(ReflectionUtils.getMethodByName(NovaRuleTestPatch::class, "checkOreNovaRuleTest")) - ifeq(cantPlaceLabel) - }) - val canPlaceMethod = VirtualClassPath[ORE_FEATURE_CAN_PLACE_ORE_METHOD] - val novaRuleLabel = canPlaceMethod.instructions.findNthOfType(2) - canPlaceMethod.instructions.insert(buildInsnList { - addLabel() - aLoad(4) - getField(TARGET_BLOCK_STATE_TARGET_FIELD) - instanceOf(NovaRuleTest::class) - ifne(novaRuleLabel) - }) + VirtualClassPath[ORE_FEATURE_DO_PLACE_METHOD].replaceEvery( + 0, 0, + { + aLoad(1) // level + invokeStatic(::canPlaceOre) + } + ) { it.opcode == Opcodes.INVOKESTATIC && (it as MethodInsnNode).calls(OreFeature::canPlaceOre) } + } + + @JvmStatic + fun canPlaceOre( + state: BlockState, + adjacentStateAccessor: Function, + random: RandomSource, + config: OreConfiguration, + targetState: OreConfiguration.TargetBlockState, + pos: BlockPos.MutableBlockPos, + level: WorldGenLevel + ): Boolean { + if (targetState.target is NovaRuleTest) { + return checkOreNovaRuleTest(state, random, pos, level, targetState) + } else { + return OreFeature.canPlaceOre(state, adjacentStateAccessor, random, config, targetState, pos) + } } private fun transformReplaceBlockFeature() { @@ -105,7 +117,7 @@ internal object NovaRuleTestPatch : MultiTransformer(setOf(OreFeature::class, Re aLoad(3) aLoad(2) aLoad(6) - invokeStatic(ReflectionUtils.getMethodByName(NovaRuleTestPatch::class, "checkOreNovaRuleTest")) + invokeStatic(::checkOreNovaRuleTest) ifne(trueLabel) addLabel() @@ -121,7 +133,7 @@ internal object NovaRuleTestPatch : MultiTransformer(setOf(OreFeature::class, Re 0, buildInsnList { aLoad(1) - invokeStatic(ReflectionUtils.getMethodByName(NovaRuleTestPatch::class, "testProcessorRule")) + invokeStatic(::testProcessorRule) } ) { it.opcode == INVOKEVIRTUAL && (it as MethodInsnNode).calls(PROCESSOR_RULE_TEST_METHOD) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/BitmapFontGenerator.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/BitmapFontGenerator.kt index 90077833c40..1d81c5fd868 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/BitmapFontGenerator.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/BitmapFontGenerator.kt @@ -12,6 +12,7 @@ import xyz.xenondevs.nova.resources.builder.font.provider.bitmap.ArrayCodePointG import xyz.xenondevs.nova.resources.builder.font.provider.bitmap.BitmapProvider import xyz.xenondevs.nova.resources.builder.font.provider.bitmap.RasterGlyphGrid import xyz.xenondevs.nova.resources.builder.font.provider.unihex.UnihexProvider +import kotlin.io.path.exists private const val GLYPH_HEIGHT = 16 @@ -53,7 +54,7 @@ internal class BitmapFontGenerator( */ private fun convertUnihexProvider(provider: UnihexProvider): List> = provider.glyphRasters.map { (width, glyphRasters) -> - val bitmapProvider = buildBitmapProvider(width, glyphRasters) + val bitmapProvider = buildBitmapProvider(provider, width, glyphRasters) bitmapProvider.write(builder) return@map bitmapProvider } @@ -62,7 +63,7 @@ internal class BitmapFontGenerator( * Builds a bitmap provider for the given [glyphRasters] of [width]. * The resulting rasters will only have one row. */ - private fun buildBitmapProvider(width: Int, glyphRasters: Int2ObjectMap): BitmapProvider { + private fun buildBitmapProvider(original: UnihexProvider, width: Int, glyphRasters: Int2ObjectMap): BitmapProvider { val glyphCount = glyphRasters.size val rasterWidth = width * glyphCount // the length of one line in the resulting raster @@ -81,12 +82,22 @@ internal class BitmapFontGenerator( } } - val id = font.id return BitmapProvider.custom( - ResourcePath(ResourceType.FontTexture, font.id.namespace, "font/${id.path}/nova_bmp/${width}x16.png"), + determineBitmapTextureFilePath(font.id, width), ArrayCodePointGrid(arrayOf(codePoints)), RasterGlyphGrid(glyphCount, 1, width, 16, raster), 8, 7 - ) + ).apply { filter.putAll(original.filter) } + } + + private fun determineBitmapTextureFilePath(id: ResourcePath, width: Int): ResourcePath { + var i = 0 + var path: ResourcePath + do { + path = ResourcePath(ResourceType.FontTexture, id.namespace, "font/${id.path}/nova_bmp/${width}x16_$i.png") + i++ + } while (builder.resolve(path).exists()) + + return path } } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/CharSizeCalculator.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/CharSizeCalculator.kt index fda38a3f7fa..2fe97b1dc9b 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/CharSizeCalculator.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/CharSizeCalculator.kt @@ -1,7 +1,10 @@ package xyz.xenondevs.nova.resources.builder import xyz.xenondevs.commons.collections.CollectionUtils +import xyz.xenondevs.commons.provider.combinedProvider import xyz.xenondevs.nova.LOGGER +import xyz.xenondevs.nova.config.MAIN_CONFIG +import xyz.xenondevs.nova.config.entry import xyz.xenondevs.nova.resources.CharSizeTable import xyz.xenondevs.nova.resources.CharSizes import xyz.xenondevs.nova.resources.builder.font.provider.ReferenceProvider @@ -9,6 +12,16 @@ import xyz.xenondevs.nova.resources.builder.task.PackTask import xyz.xenondevs.nova.resources.builder.task.PackTaskHolder import xyz.xenondevs.nova.resources.builder.task.font.FontContent +private val SETTINGS: Set by combinedProvider( + MAIN_CONFIG.entry("resource_pack", "force_uniform_font"), + MAIN_CONFIG.entry("resource_pack", "japanese_glyph_variants") +) { uniform, jp -> + buildSet { + if (uniform) add("uniform") + if (jp) add("jp") + } +} + class CharSizeCalculator internal constructor(builder: ResourcePackBuilder) : PackTaskHolder { private val fontContent by builder.getHolderLazily() @@ -31,6 +44,10 @@ class CharSizeCalculator internal constructor(builder: ResourcePackBuilder) : Pa val id = font.id val table = CharSizeTable() for (provider in font.providers.reversed()) { + // skip providers that have mismatching filters + if (provider.filter.any { (filterKey, filterValue) -> filterValue != filterKey in SETTINGS }) + continue + if (provider is ReferenceProvider) { val referencedTable = CharSizes.getTable(provider.id) ?: throw IllegalStateException("Referenced font ${provider.id} has no char size table") diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/ResourcePackBuilder.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/ResourcePackBuilder.kt index 927b744a9ab..8d11ca6392e 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/ResourcePackBuilder.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/ResourcePackBuilder.kt @@ -5,15 +5,17 @@ import com.google.common.jimfs.Jimfs import com.google.gson.JsonObject import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json +import net.minecraft.SharedConstants +import net.minecraft.server.packs.PackType import xyz.xenondevs.commons.collections.enumMap import xyz.xenondevs.commons.provider.MutableProvider import xyz.xenondevs.commons.provider.Provider import xyz.xenondevs.commons.provider.combinedProvider +import xyz.xenondevs.commons.provider.flatMap import xyz.xenondevs.commons.provider.flattenIterables import xyz.xenondevs.commons.provider.map -import xyz.xenondevs.commons.provider.mapNonNull import xyz.xenondevs.commons.provider.mutableProvider -import xyz.xenondevs.commons.provider.orElse +import xyz.xenondevs.commons.provider.provider import xyz.xenondevs.downloader.ExtractionMode import xyz.xenondevs.downloader.MinecraftAssetsDownloader import xyz.xenondevs.nova.DATA_FOLDER @@ -115,10 +117,12 @@ class ResourcePackBuilder internal constructor() { companion object { + /** + * The resource pack format version of the current Minecraft version. + */ + val PACK_VERSION = SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES) + private val JIMFS_PROVIDER: MutableProvider = mutableProvider { Jimfs.newFileSystem(Configuration.unix()) } - private val FILE_SYSTEM_PROVIDER: Provider = combinedProvider( - IN_MEMORY_PROVIDER, JIMFS_PROVIDER - ) { inMemory, jimfs -> if (inMemory) jimfs else FileSystems.getDefault() } // val RESOURCE_PACK_FILE: Path = DATA_FOLDER.resolve("resource_pack/ResourcePack.zip") @@ -129,7 +133,11 @@ class ResourcePackBuilder internal constructor() { // // - private val RESOURCE_PACK_BUILD_DIR_PROVIDER: Provider = FILE_SYSTEM_PROVIDER.mapNonNull { it.rootDirectories.first() }.orElse(RESOURCE_PACK_DIR.resolve(".build")) + private val RESOURCE_PACK_BUILD_DIR_PROVIDER: Provider = IN_MEMORY_PROVIDER.flatMap { inMemory -> + if (inMemory) + JIMFS_PROVIDER.map { it.rootDirectories.first() } + else provider(RESOURCE_PACK_DIR.resolve(".build")) + } private val TEMP_BASE_PACKS_DIR_PROVIDER: Provider = RESOURCE_PACK_BUILD_DIR_PROVIDER.map { it.resolve("base_packs") } private val PACK_DIR_PROVIDER: Provider = RESOURCE_PACK_BUILD_DIR_PROVIDER.map { it.resolve("pack") } private val ASSETS_DIR_PROVIDER: Provider = PACK_DIR_PROVIDER.map { it.resolve("assets") } @@ -310,10 +318,7 @@ class ResourcePackBuilder internal constructor() { private fun writeMetadata(assetPacks: Int, basePacks: Int) { val packMcmetaObj = JsonObject() val packObj = JsonObject().also { packMcmetaObj.add("pack", it) } - packObj.addProperty("pack_format", 15) - val supportedFormats = JsonObject().also { packObj.add("supported_formats", it) } - supportedFormats.addProperty("min_inclusive", 0) - supportedFormats.addProperty("max_inclusive", 999) + packObj.addProperty("pack_format", PACK_VERSION) packObj.addProperty("description", PACK_DESCRIPTION.format(assetPacks, basePacks)) PACK_MCMETA_FILE.parent.createDirectories() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/BasePacks.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/BasePacks.kt index 1075ddf3194..8a73d8b277f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/BasePacks.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/BasePacks.kt @@ -1,5 +1,6 @@ package xyz.xenondevs.nova.resources.builder.basepack +import net.minecraft.world.level.block.state.BlockState import org.bukkit.Material import xyz.xenondevs.commons.provider.map import xyz.xenondevs.nova.LOGGER @@ -9,14 +10,15 @@ import xyz.xenondevs.nova.resources.ResourcePath import xyz.xenondevs.nova.resources.ResourceType import xyz.xenondevs.nova.resources.builder.ResourcePackBuilder import xyz.xenondevs.nova.resources.builder.basepack.merger.FileMerger +import xyz.xenondevs.nova.resources.builder.data.PackMcMeta import xyz.xenondevs.nova.resources.builder.task.font.MovedFontContent -import xyz.xenondevs.nova.util.StringUtils +import xyz.xenondevs.nova.resources.lookup.ResourceLookups +import xyz.xenondevs.nova.util.data.readJson import xyz.xenondevs.nova.util.data.useZip import xyz.xenondevs.nova.world.block.state.model.BackingStateConfigType import java.io.File import java.nio.file.Path import kotlin.io.path.copyTo -import kotlin.io.path.copyToRecursively import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.extension @@ -48,54 +50,90 @@ class BasePacks internal constructor(private val builder: ResourcePackBuilder) { internal val occupiedSolidIds = HashMap, HashSet>() internal fun include() { - packs.map { - if (it.isFile && it.extension.equals("zip", true)) { - val dir = ResourcePackBuilder.TEMP_BASE_PACKS_DIR.resolve("${it.nameWithoutExtension}-${StringUtils.randomString(5)}") - dir.createDirectories() - it.useZip { zip -> zip.copyToRecursively(dir, followLinks = false, overwrite = true) } + for (pack in packs) { + if (pack.isFile && pack.extension.equals("zip", true)) { + pack.useZip { zip -> mergeBasePack(zip) } + } else if (pack.isDirectory) { + mergeBasePack(pack.toPath()) + } + } + + val occupiedBlockStates: Set = occupiedSolidIds.entries.map { (type, ids) -> + buildSet { + for (id in ids) { + add(type.of(id, false).vanillaBlockState) + if (type.isWaterloggable) { + add(type.of(id, true).vanillaBlockState) + } + } + } + }.flatten().toHashSet() + + if (occupiedBlockStates.isNotEmpty()) + LOGGER.warn("Base packs occupy ${occupiedBlockStates.size} block states that cannot be used by Nova") + + ResourceLookups.OCCUPIED_BLOCK_STATES = occupiedBlockStates + } + + private fun mergeBasePack(packDir: Path) { + try { + val packMcMetaFile = packDir.resolve("pack.mcmeta") + if (!packMcMetaFile.exists()) { + LOGGER.warn("Skipping base pack $packDir: No pack.mcmeta present") + return + } + + val packMcMeta = packMcMetaFile.readJson(true) + LOGGER.info("Merging base pack \"${packMcMeta.pack.description}\"") + + val assetDirs: List = buildList { + add(packDir.resolve("assets")) - return@map dir + packMcMeta.overlays?.entries + ?.filter { entry -> ResourcePackBuilder.PACK_VERSION in entry.formats } + ?.forEach { entry -> add(packDir.resolve("${entry.directory}/assets")) } + }.filter { it.exists() } + + for (assetDir in assetDirs) { + mergeAssetsDir(assetDir) } - return@map it.toPath() - }.forEach { - mergeBasePack(it) - requestMovedFonts(it) + requestMovedFonts(packDir) + } catch (e: Exception) { + LOGGER.error("Failed to merge base pack in $packDir", e) } } - private fun mergeBasePack(packDir: Path) { - LOGGER.info("Adding base pack $packDir") - packDir.walk() + private fun mergeAssetsDir(assetsDir: Path) { + assetsDir.walk() .filter(Path::isRegularFile) - .forEach { file -> + .forEach { sourceFile -> // Validate file extension - if (file.extension.lowercase() !in WHITELISTED_FILE_TYPES) { - LOGGER.warn("Skipping file $file as it is not a resource pack file") + if (sourceFile.extension.lowercase() !in WHITELISTED_FILE_TYPES) { + LOGGER.warn("Skipping file $sourceFile as it is not a resource pack file") return@forEach } // Validate file name - if (!ResourcePath.isValidPath(file.name)) { - LOGGER.warn("Skipping file $file as its name does not match regex [a-z0-9_.-]") + if (!ResourcePath.isValidPath(sourceFile.name)) { + LOGGER.warn("Skipping file $sourceFile as its name does not match regex [a-z0-9_.-]") return@forEach } - val relPath = file.relativeTo(packDir) - val packFile = ResourcePackBuilder.PACK_DIR.resolve(relPath.invariantSeparatorsPathString) + // normalize assets dir name to "assets" + val relPath = "assets/" + sourceFile.relativeTo(assetsDir).invariantSeparatorsPathString + val destFile = ResourcePackBuilder.PACK_DIR.resolve(relPath) - packFile.parent.createDirectories() + destFile.parent.createDirectories() val fileMerger = mergers.firstOrNull { it.acceptsFile(relPath) } if (fileMerger != null) { try { - fileMerger.merge(file, packFile, packDir, relPath) + fileMerger.merge(sourceFile, destFile) } catch (t: Throwable) { - LOGGER.error("An exception occurred trying to merge base pack file \"$file\" with \"$packFile\"", t) + LOGGER.error("An exception occurred trying to merge base pack file \"$sourceFile\" with \"$destFile\"", t) } - } else if (!packFile.exists()) { - file.copyTo(packFile) } else { - LOGGER.warn("Skipping file $file: File type cannot be merged") + sourceFile.copyTo(destFile, overwrite = true) } } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/merger/FileMerger.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/merger/FileMerger.kt index 932892ee4b2..cb1a6921bc8 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/merger/FileMerger.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/basepack/merger/FileMerger.kt @@ -5,9 +5,8 @@ import java.nio.file.Path internal abstract class FileMerger(protected val basePacks: BasePacks) { - abstract fun acceptsFile(relPath: Path): Boolean + abstract fun acceptsFile(relPath: String): Boolean - open fun merge(source: Path, destination: Path, baseDir: Path, relPath: Path) = merge(source, destination) open fun merge(source: Path, destination: Path) = Unit companion object { @@ -24,7 +23,7 @@ internal abstract class FileMerger(protected val basePacks: BasePacks) { internal abstract class FileInDirectoryMerger(basePacks: BasePacks, val path: String) : FileMerger(basePacks) { - override fun acceptsFile(relPath: Path): Boolean { + override fun acceptsFile(relPath: String): Boolean { return relPath.startsWith(path) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/data/PackMcMeta.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/data/PackMcMeta.kt new file mode 100644 index 00000000000..cc1be087713 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/data/PackMcMeta.kt @@ -0,0 +1,65 @@ +package xyz.xenondevs.nova.resources.builder.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import xyz.xenondevs.nova.serialization.kotlinx.IntRangeMultiFormatSerializer + +@Serializable +internal data class PackMcMeta( + val pack: Pack, + val features: Features? = null, + val filter: Filters? = null, + val overlays: Overlays? = null, + val language: Map = emptyMap() +) { + + @Serializable + internal data class Pack( + val description: String, + @SerialName("pack_format") + val packFormat: Int, + @SerialName("supported_formats") + @Serializable(with = IntRangeMultiFormatSerializer::class) + val supportedFormats: IntRange? = null + ) + + @Serializable + internal data class Filters( + val block: List + ) { + + @Serializable + internal data class Filter( + val namespace: String, + val path: String + ) + + } + + @Serializable + internal data class Features( + val enabled: List + ) + + @Serializable + internal data class Overlays( + val entries: List + ) { + + @Serializable + internal data class Entry( + val directory: String, + @Serializable(with = IntRangeMultiFormatSerializer::class) + val formats: IntRange + ) + + } + + @Serializable + internal data class Language( + val name: String, + val region: String, + val bidirectional: Boolean + ) + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/FontProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/FontProvider.kt index 50f6040273d..4b5dc43707d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/FontProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/FontProvider.kt @@ -7,8 +7,10 @@ import xyz.xenondevs.commons.gson.getString import xyz.xenondevs.nova.resources.builder.ResourcePackBuilder import xyz.xenondevs.nova.resources.builder.font.provider.bitmap.MutableBitmapProvider import xyz.xenondevs.nova.resources.builder.font.provider.unihex.UnihexProvider +import xyz.xenondevs.nova.serialization.json.addSerialized +import xyz.xenondevs.nova.serialization.json.getDeserialized -abstract class FontProvider internal constructor() { +abstract class FontProvider internal constructor(private val type: String) { /** * The code points that this [FontProvider] supplies. @@ -29,10 +31,23 @@ abstract class FontProvider internal constructor() { */ internal abstract val charSizes: Int2ObjectMap + /** + * Filter options that determine when this font provider should be used. + * + * * `uniform`: Optional. The value that "Force Uniform" must be for this font provider to be enabled. + * * `jp`: Optional. The value that "Japanese Glyph Variants" must be for this font provider to be enabled. + */ + open val filter: MutableMap = HashMap() + /** * Creates a [JsonObject] representation of this [FontProvider]. */ - abstract fun toJson(): JsonObject + open fun toJson(): JsonObject { + return JsonObject().apply { + addProperty("type", type) + if (filter.isNotEmpty()) addSerialized("filter", filter) + } + } /** * Writes additional data to the assets directory, such as bitmaps or unihex files. @@ -41,14 +56,21 @@ abstract class FontProvider internal constructor() { companion object { - fun fromDisk(builder: ResourcePackBuilder, provider: JsonObject): FontProvider = - when (val type = provider.getString("type")) { + fun fromDisk(builder: ResourcePackBuilder, provider: JsonObject): FontProvider { + val fontProvider = when (val type = provider.getString("type")) { "reference" -> ReferenceProvider.of(provider) "space" -> SpaceProvider.of(provider) "bitmap" -> MutableBitmapProvider.fromDisk(builder, provider) "unihex" -> UnihexProvider.fromDisk(builder, provider) else -> throw UnsupportedOperationException("Unsupported font provider type: $type") } + + if (provider.has("filter")) { + fontProvider.filter.putAll(provider.getDeserialized>("filter")) + } + + return fontProvider + } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/ReferenceProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/ReferenceProvider.kt index debc8cbc77a..d9081e8e8bc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/ReferenceProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/ReferenceProvider.kt @@ -10,7 +10,7 @@ import xyz.xenondevs.nova.resources.ResourceType /** * Represents a `reference` font provider. */ -class ReferenceProvider(var id: ResourcePath) : FontProvider() { +class ReferenceProvider(var id: ResourcePath) : FontProvider("reference") { override val codePoints: IntSet get() = throw UnsupportedOperationException("Cannot retrieve codePoints from reference provider") @@ -18,8 +18,7 @@ class ReferenceProvider(var id: ResourcePath) : FontProvider( override val charSizes: Int2ObjectMap get() = throw UnsupportedOperationException("Cannot retrieve charSizes from reference provider") - override fun toJson() = JsonObject().apply { - addProperty("type", "reference") + override fun toJson() = super.toJson().apply { addProperty("id", id.toString()) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/SpaceProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/SpaceProvider.kt index 9e5524abf0d..66103872ce5 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/SpaceProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/SpaceProvider.kt @@ -11,7 +11,7 @@ import xyz.xenondevs.commons.gson.getObject /** * Represents a `space` font provider. */ -class SpaceProvider(val advances: Int2FloatMap) : FontProvider() { +class SpaceProvider(val advances: Int2FloatMap) : FontProvider("space") { override val codePoints: IntSet get() = advances.keys @@ -27,8 +27,7 @@ class SpaceProvider(val advances: Int2FloatMap) : FontProvider() { return sizes } - override fun toJson() = JsonObject().apply { - addProperty("type", "space") + override fun toJson() = super.toJson().apply { add("advances", JsonObject().apply { for ((codePoint, width) in advances.int2FloatEntrySet()) addProperty(Character.toString(codePoint), width) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/BitmapProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/BitmapProvider.kt index fd0983d41c1..5be47b4afdc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/BitmapProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/BitmapProvider.kt @@ -1,6 +1,5 @@ package xyz.xenondevs.nova.resources.builder.font.provider.bitmap -import com.google.gson.JsonObject import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntSet @@ -16,7 +15,7 @@ import kotlin.math.roundToInt /** * Represents a `bitmap` font provider. */ -abstract class BitmapProvider internal constructor() : FontProvider() { +abstract class BitmapProvider internal constructor() : FontProvider("bitmap") { /** * A [ResourcePath] to the texture file. @@ -61,8 +60,7 @@ abstract class BitmapProvider internal constructor() : FontProvider() { builder.writeImage(file, glyphGrid.toImage()) } - override fun toJson() = JsonObject().apply { - addProperty("type", "bitmap") + override fun toJson() = super.toJson().apply { addProperty("file", file.toString()) if (height != 8) addProperty("height", height) addProperty("ascent", ascent) @@ -130,6 +128,8 @@ abstract class BitmapProvider internal constructor() : FontProvider() { get() = delegate.codePointGrid override val glyphGrid: GlyphGrid get() = delegate.glyphGrid + override val filter: MutableMap + get() = delegate.filter override val charSizes by lazy(::calculateCharSizes) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/MutableBitmapProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/MutableBitmapProvider.kt index 239882d4929..06b30f94197 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/MutableBitmapProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/bitmap/MutableBitmapProvider.kt @@ -101,8 +101,7 @@ abstract class MutableBitmapProvider : BitmapProvider() { return ReferenceGlyphGrid.of(img, glyphWidth, glyphHeight) } - override fun toJson() = JsonObject().apply { - addProperty("type", "bitmap") + override fun toJson() = super.toJson().apply { addProperty("file", file.toString()) addProperty("height", height) addProperty("ascent", ascent) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/unihex/UnihexProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/unihex/UnihexProvider.kt index c59ee764d58..f5cebff0b78 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/unihex/UnihexProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/font/provider/unihex/UnihexProvider.kt @@ -34,7 +34,7 @@ data class SizeOverride(val from: Int, val to: Int, val left: Int, val right: In */ abstract class UnihexProvider internal constructor( val hexFile: ResourcePath -) : FontProvider() { +) : FontProvider("unihex") { protected abstract val sizeOverrides: ObjectList protected abstract val glyphs: UnihexGlyphs @@ -205,8 +205,7 @@ abstract class UnihexProvider internal constructor( } } - override fun toJson() = JsonObject().apply { - addProperty("type", "unihex") + override fun toJson() = super.toJson().apply { addProperty("hex_file", hexFile.toString()) addSerialized("size_overrides", sizeOverrides) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/layout/item/ItemModelDefinitionBuilder.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/layout/item/ItemModelDefinitionBuilder.kt index 5ecda7386d9..0470967bccb 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/layout/item/ItemModelDefinitionBuilder.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/layout/item/ItemModelDefinitionBuilder.kt @@ -1,5 +1,7 @@ package xyz.xenondevs.nova.resources.builder.layout.item +import org.joml.Vector3d +import xyz.xenondevs.commons.collections.enumMapOf import xyz.xenondevs.nova.registry.RegistryElementBuilderDsl import xyz.xenondevs.nova.resources.ResourcePath import xyz.xenondevs.nova.resources.ResourceType @@ -9,9 +11,14 @@ import xyz.xenondevs.nova.resources.builder.data.EmptyItemModel import xyz.xenondevs.nova.resources.builder.data.ItemModel import xyz.xenondevs.nova.resources.builder.data.ItemModelDefinition import xyz.xenondevs.nova.resources.builder.data.SpecialItemModel.SpecialModel +import xyz.xenondevs.nova.resources.builder.data.TintSource import xyz.xenondevs.nova.resources.builder.layout.ModelSelectorScope import xyz.xenondevs.nova.resources.builder.layout.block.BlockModelSelectorScope +import xyz.xenondevs.nova.resources.builder.model.Model import xyz.xenondevs.nova.resources.builder.model.ModelBuilder +import xyz.xenondevs.nova.resources.builder.task.model.ModelContent +import xyz.xenondevs.nova.ui.menu.Canvas +import java.awt.Color @RegistryElementBuilderDsl class ItemModelDefinitionBuilder internal constructor( @@ -195,4 +202,80 @@ sealed class ItemModelCreationScope( } } + /** + * Creates a canvas model, which is an item model with a flat texture where each pixel is individually + * addressable using the `colors` part of the `minecraft:custom_model_data` component, where + * the pixel at (x, y) is found under the index `y * width + x` and (0, 0) is the top-left pixel. + * + * The maximum [width] and [height] are `161 - |offsetX|` and `161 - |offsetY|` respectively. + * (Consider using multiple smaller canvases instead of a single large one to reduce the resource pack size.) + * + * @param width The width of the canvas, in pixels. + * @param height The height of the canvas, in pixels. + * @param offsetX The x offset of the canvas texture to the item's center, pixels. + * @param offsetY The y offset of the canvas texture to the item's center, pixels. + * @param scale The size of the pixels in the canvas texture. + * A scale of 2 means each pixel is 2x2 pixels in game (assuming a client-side gui-scale of 1). + * Defaults to 1. + * + * @see Canvas + */ + fun canvasModel( + width: Int, height: Int, + offsetX: Double = 0.0, offsetY: Double = 0.0, + scale: Double = 1.0 + ): ItemModel = select(SelectItemModelProperty.DisplayContext) { + // the actual width and height of the canvas, in pixel models needed, takes scale into account + val actualWidth = (width / scale).toInt() + val actualHeight = (height / scale).toInt() + + // the individual pixel models apply a display scale of 4, so actualScale counteracts this with 0.25 + val actualScale = 0.25 * scale + + // parent model for all pixels with this scale + val parentModel = Model( + parent = ResourcePath(ResourceType.Model, "nova", "item/canvas_pixel"), + elements = listOf( + Model.Element( + from = Vector3d(8.0, 8.0 - actualScale, 0.0), + to = Vector3d(8.0 + actualScale, 8.0, 0.0), + faces = enumMapOf( + Model.Direction.SOUTH to Model.Element.Face( + texture = "#0", + tintIndex = 0 + ) + ) + ) + ) + ) + val parentModelId = resourcePackBuilder.getHolder().getOrPutGenerated(parentModel) + + fallback = empty() + case[DisplayContext.GUI] = composite { + for (y in 0..): ModelBuilder { - if (!background && !stretched) + @JvmOverloads // TODO remove in 0.19 + fun createGuiModel(background: Boolean, stretched: Boolean, vararg layers: ResourcePath, display: Model.Display? = null): ModelBuilder { + if (!background && !stretched && display == null) return createLayeredModel(*layers) val elements = ArrayList() @@ -124,7 +130,11 @@ class ItemModelSelectorScope internal constructor( ) } - val parent = Model(ResourcePath(ResourceType.Model, "nova", "item/gui_item"), elements = elements) + val parent = Model( + ResourcePath(ResourceType.Model, "nova", "item/gui_item"), + elements = elements, + display = if (display != null) mapOf(Model.Display.Position.GUI to display) else emptyMap() + ) val parentId = modelContent.getOrPutGenerated(parent) val textures = HashMap() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/task/font/MovedFontContent.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/task/font/MovedFontContent.kt index cbcb9deea21..67690aa9995 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/task/font/MovedFontContent.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/builder/task/font/MovedFontContent.kt @@ -72,7 +72,9 @@ class MovedFontContent internal constructor(private val builder: ResourcePackBui is ReferenceProvider -> { val id = provider.id requestMovedFont(id, y) - ReferenceProvider(ResourcePath(ResourceType.Font, id.namespace, id.path + "/$y")) + ReferenceProvider(ResourcePath(ResourceType.Font, id.namespace, id.path + "/$y")).apply { + filter.putAll(provider.filter) + } } else -> provider diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/lookup/ResourceLookups.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/lookup/ResourceLookups.kt index 0684cac62ec..4d646e5a549 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/resources/lookup/ResourceLookups.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/resources/lookup/ResourceLookups.kt @@ -2,6 +2,7 @@ package xyz.xenondevs.nova.resources.lookup +import net.minecraft.world.level.block.state.BlockState import xyz.xenondevs.nova.config.PermanentStorage import xyz.xenondevs.nova.resources.builder.task.RuntimeEquipmentData import xyz.xenondevs.nova.resources.builder.task.font.FontChar @@ -10,7 +11,6 @@ import xyz.xenondevs.nova.ui.overlay.guitexture.GuiTexture import xyz.xenondevs.nova.world.block.state.NovaBlockState import xyz.xenondevs.nova.world.block.state.model.LinkedBlockModelProvider import xyz.xenondevs.nova.world.item.Equipment -import java.util.* import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -84,6 +84,17 @@ internal object ResourceLookups { val TEXTURE_ICON_LOOKUP: IdResourceLookup = idResourceLookup("texture_icon_lookup") + /** + * Lookup containing all block states that are in use by base packs. + */ + val OCCUPIED_BLOCK_STATES_LOOKUP: ResourceLookup> = + resourceLookup("occupied_block_states", emptySet(), typeOf>()) + + /** + * Set of all block states that are in use by base packs. + */ + var OCCUPIED_BLOCK_STATES: Set by OCCUPIED_BLOCK_STATES_LOOKUP + private inline fun resourceLookup(key: String, empty: T): ResourceLookup { val lookup = ResourceLookup(key, typeOf(), empty) lookups += lookup diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/Gson.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/Gson.kt index 5bb853ef987..976d7bebb85 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/Gson.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/Gson.kt @@ -9,6 +9,7 @@ import xyz.xenondevs.commons.gson.registerTypeHierarchyAdapter import xyz.xenondevs.commons.gson.toJsonTreeTyped import xyz.xenondevs.nova.registry.NovaRegistries import xyz.xenondevs.nova.serialization.json.serializer.BackingStateConfigSerialization +import xyz.xenondevs.nova.serialization.json.serializer.BackingStateConfigTypeSerialization import xyz.xenondevs.nova.serialization.json.serializer.BlockDataTypeAdapter import xyz.xenondevs.nova.serialization.json.serializer.BlockStateSerialization import xyz.xenondevs.nova.serialization.json.serializer.BlockStateVariantDataSerialization @@ -47,6 +48,7 @@ private val GSON_BUILDER = GsonBuilder() .registerTypeHierarchyAdapter(BlockStateSerialization.nullSafe()) .registerTypeHierarchyAdapter(NovaBlockStateSerialization) .registerTypeHierarchyAdapter(BlockStateVariantDataSerialization) + .registerTypeHierarchyAdapter(BackingStateConfigTypeSerialization) .registerTypeHierarchyAdapter(BackingStateConfigSerialization) .registerTypeHierarchyAdapter(LinkedBlockModelProviderSerialization) .registerTypeHierarchyAdapter(Matrix4fcTypeAdapter.nullSafe()) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigSerialization.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigSerialization.kt index 5b603ded88a..002a0b0032c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigSerialization.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigSerialization.kt @@ -6,20 +6,18 @@ import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer +import xyz.xenondevs.commons.gson.deserializeTyped import xyz.xenondevs.commons.gson.getBoolean import xyz.xenondevs.commons.gson.getInt -import xyz.xenondevs.commons.gson.getString import xyz.xenondevs.nova.world.block.state.model.BackingStateConfig import xyz.xenondevs.nova.world.block.state.model.BackingStateConfigType import java.lang.reflect.Type -import kotlin.reflect.full.companionObjectInstance -import kotlin.reflect.jvm.jvmName internal object BackingStateConfigSerialization : JsonSerializer, JsonDeserializer { override fun serialize(src: BackingStateConfig, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { val obj = JsonObject() - obj.addProperty("type", src.type::class.jvmName) + obj.add("type", context.serialize(src.type)) obj.addProperty("id", src.id) obj.addProperty("waterlogged", src.waterlogged) return obj @@ -28,8 +26,7 @@ internal object BackingStateConfigSerialization : JsonSerializer + val type = context.deserializeTyped>(json.get("type")) return type.of(json.getInt("id"), json.getBoolean("waterlogged")) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigTypeSerialization.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigTypeSerialization.kt new file mode 100644 index 00000000000..905c6fca1c4 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/BackingStateConfigTypeSerialization.kt @@ -0,0 +1,25 @@ +package xyz.xenondevs.nova.serialization.json.serializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import xyz.xenondevs.nova.world.block.state.model.BackingStateConfigType +import java.lang.reflect.Type +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.jvm.jvmName + +internal object BackingStateConfigTypeSerialization : JsonSerializer>, JsonDeserializer> { + + override fun serialize(src: BackingStateConfigType<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src::class.jvmName) + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): BackingStateConfigType<*> { + val kclass = Class.forName(json.asString).kotlin + return (kclass.objectInstance ?: kclass.companionObjectInstance) as BackingStateConfigType<*> + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/RecipeDeserializer.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/RecipeDeserializer.kt index 8d5284d0ae3..800defca197 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/RecipeDeserializer.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/json/serializer/RecipeDeserializer.kt @@ -70,8 +70,22 @@ interface RecipeDeserializer { return ItemUtils.getRecipeChoice(names) } - fun getRecipeKey(file: File): NamespacedKey = - NamespacedKey("nova", "${file.parentFile.name}.${file.nameWithoutExtension}") + /** + * Generates a [NamespacedKey] for a recipe file, assuming that is located under + * `plugins//recipes///.json`, which + * would generate the following key: `://` + */ + fun getRecipeKey(file: File): NamespacedKey { + val relativePathString = file.relativeTo(File("plugins/")).invariantSeparatorsPath + val addonId = relativePathString.substringBefore('/').lowercase() + return NamespacedKey( + addonId, + relativePathString + .substringAfter('/') // Remove the addon id + .substringAfter('/') // remove "recipes" + .substringBeforeLast('.') // remove extension + ) + } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/kotlinx/IntRangeSerializer.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/kotlinx/IntRangeSerializer.kt new file mode 100644 index 00000000000..a9d1f6642ec --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/serialization/kotlinx/IntRangeSerializer.kt @@ -0,0 +1,58 @@ +package xyz.xenondevs.nova.serialization.kotlinx + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.JsonTransformingSerializer + +/** + * Serializes [IntRange] as an array of two integers, where the first integer is the minimum value + * and the second integer is the maximum value, both inclusive. + */ +internal object IntRangeSerializer : KSerializer { + + private val delegateSerializer = IntArraySerializer() + override val descriptor = SerialDescriptor("xyz.xenondevs.nova.IntRangeSerializer", delegateSerializer.descriptor) + + override fun serialize(encoder: Encoder, value: IntRange) { + delegateSerializer.serialize(encoder, intArrayOf(value.first, value.last)) + } + + override fun deserialize(decoder: Decoder): IntRange { + val array = delegateSerializer.deserialize(decoder) + require(array.size == 2) { "Expected array of size 2, but got size ${array.size}" } + return IntRange(array[0], array[1]) + } + +} + +/** + * Can deserialize the following formats for an [IntRange]: + * + * * `range: 1` -> `[1, 1]` + * * `range: [1, 2]` -> `[1, 2]` + * * `range: { "min_inclusive": 1, "max_inclusive": 2 }` -> `[1, 2]` + * + */ +internal object IntRangeMultiFormatSerializer : JsonTransformingSerializer(IntRangeSerializer) { + + override fun transformDeserialize(element: JsonElement): JsonElement = + when (element) { + is JsonArray -> element + is JsonPrimitive -> JsonArray(listOf(element, element)) + is JsonObject -> { + val min = element["min_inclusive"] + ?: throw NoSuchElementException("Missing 'min_inclusive' key in IntRange object") + val max = element["max_inclusive"] + ?: throw NoSuchElementException("Missing 'max_inclusive' key in IntRange object") + JsonArray(listOf(min, max)) + } + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/Canvas.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/Canvas.kt new file mode 100644 index 00000000000..ace9e0c0295 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/Canvas.kt @@ -0,0 +1,117 @@ +package xyz.xenondevs.nova.ui.menu + +import it.unimi.dsi.fastutil.ints.IntArrayList +import net.minecraft.core.component.DataComponents +import net.minecraft.world.item.component.CustomModelData +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import xyz.xenondevs.invui.item.AbstractItem +import xyz.xenondevs.invui.item.Click +import xyz.xenondevs.invui.item.Item +import xyz.xenondevs.invui.item.ItemBuilder +import xyz.xenondevs.invui.item.ItemProvider +import xyz.xenondevs.invui.item.notifyWindows +import xyz.xenondevs.nova.resources.builder.layout.item.ItemModelCreationScope +import xyz.xenondevs.nova.util.unwrap +import xyz.xenondevs.nova.world.item.DefaultGuiItems +import xyz.xenondevs.nova.world.item.NovaItem +import java.awt.image.BufferedImage +import java.util.function.Supplier + +/** + * An [Item] supplier for the [canvasItem] that splits [image] into square parts of [itemResolution]x[itemResolution] px. + * + * @param canvasItem The canvas item, which uses a model created via [ItemModelCreationScope.canvasModel]. + * This model should be square and should completely fill an entire slot (18x18 px or multiples of that at non-1 scales). + * @param itemResolution The size of the canvas item in pixels (18 px or multiples of that at non-1 scales). + * @param image The image that is used to fill the canvas. Will be read from every time [notifyWindows] is called. + * + * @see ItemModelCreationScope.canvasModel + */ +open class Canvas( + private val canvasItem: NovaItem, + private val itemResolution: Int, + private val image: BufferedImage +) : Supplier { + + private val items = ArrayList() + private var supplierIndex = 0 + + /** + * Creates a new [Canvas] with the [DefaultGuiItems.CANVAS] (18x18 px) item. + * + * @param image The image that is used to fill the canvas. Will be read from every time [notifyWindows] is called. + */ + constructor(image: BufferedImage) : this(DefaultGuiItems.CANVAS, 18, image) + + init { + require(image.height % itemResolution == 0) { "Image height needs to be divisible by $itemResolution" } + require(image.width % itemResolution == 0) { "Image width needs to be divisible by $itemResolution" } + + for (y in 0..<(image.height / itemResolution)) { + for (x in 0..<(image.width / itemResolution)) { + items += CanvasPart(x, y) + } + } + } + + override fun get(): Item { + return items[supplierIndex++] + } + + /** + * [Notifies][Item.notifyWindows] all windows of all items of this canvas. + */ + fun notifyWindows() { + items.notifyWindows() + } + + /** + * Modifies the [itemBuilder] for the canvas part at the given [x] and [y] coordinates, + * which will be displayed to [viewer]. + */ + protected open fun modifyItemBuilder(x: Int, y: Int, viewer: Player, itemBuilder: ItemBuilder) = Unit + + /** + * Handles a [click] on the canvas part at the given [x] and [y] coordinates. + */ + protected open fun handleClick(x: Int, y: Int, click: Click) = Unit + + private inner class CanvasPart(private val x: Int, private val y: Int) : AbstractItem() { + + private val colors = IntArray(itemResolution * itemResolution) + + override fun getItemProvider(viewer: Player): ItemProvider { + // read colors from image + image.getRGB( + x * itemResolution, y * itemResolution, + itemResolution, itemResolution, + colors, + 0, + itemResolution + ) + + // write colors to item stack + val itemStack = canvasItem.clientsideProvider.get().unwrap().copy() + itemStack.set( + DataComponents.CUSTOM_MODEL_DATA, + CustomModelData( + emptyList(), + emptyList(), + emptyList(), + IntArrayList(colors) + ) + ) + + val builder = ItemBuilder(itemStack.asBukkitMirror()) + modifyItemBuilder(x, y, viewer, builder) + return builder + } + + override fun handleClick(clickType: ClickType, player: Player, click: Click) { + handleClick(x, y, click) + } + + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/ColorPickerWindow.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/ColorPickerWindow.kt index 7187162c5d3..77d672b7847 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/ColorPickerWindow.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/ColorPickerWindow.kt @@ -14,6 +14,7 @@ import xyz.xenondevs.nova.ui.overlay.guitexture.DefaultGuiTextures import xyz.xenondevs.nova.world.item.DefaultGuiItems import java.awt.Color +@Deprecated("Color picker will be removed in a future version") class ColorPickerWindow( private val colorPreviewItem: ColorPreviewItem, color: Color, @@ -84,6 +85,7 @@ private class ChangeColorItem( localizedName, builder ) +@Deprecated("Color picker will be removed in a future version") abstract class ColorPreviewItem(color: Color) : AbstractItem() { var color: Color = color @@ -96,6 +98,7 @@ abstract class ColorPreviewItem(color: Color) : AbstractItem() { } +@Deprecated("Color picker will be removed in a future version") class OpenColorPickerWindowItem(private val window: ColorPickerWindow) : AbstractItem() { override fun getItemProvider(player: Player) = DefaultGuiItems.TP_COLOR_PICKER.clientsideProvider diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/EnergyBar.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/EnergyBar.kt index 1fbc18c0e1a..b4c424c11cc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/EnergyBar.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/EnergyBar.kt @@ -8,22 +8,26 @@ import xyz.xenondevs.nova.util.NumberFormatUtils import xyz.xenondevs.nova.world.block.tileentity.network.type.energy.EnergyNetwork import xyz.xenondevs.nova.world.block.tileentity.network.type.energy.holder.DefaultEnergyHolder import xyz.xenondevs.nova.world.item.DefaultGuiItems +import xyz.xenondevs.nova.world.item.NovaItem /** * A multi-item gui component for displaying energy levels. */ -class EnergyBar( +class EnergyBar @JvmOverloads constructor( // TODO: Remove @JvmOverloads in 0.19 height: Int, private val energy: Provider, private val maxEnergy: Provider, private val getEnergyPlus: () -> Long, - private val getEnergyMinus: () -> Long + private val getEnergyMinus: () -> Long, + private val item: NovaItem = DefaultGuiItems.BAR_RED ) : VerticalBar(height) { - constructor(height: Int, energyHolder: DefaultEnergyHolder) : this( + @JvmOverloads + constructor(height: Int, energyHolder: DefaultEnergyHolder, item: NovaItem = DefaultGuiItems.BAR_RED) : this( height, energyHolder.energyProvider, energyHolder.maxEnergyProvider, - { energyHolder.energyPlus }, { energyHolder.energyMinus } + { energyHolder.energyPlus }, { energyHolder.energyMinus }, + item ) override fun createBarItem(section: Int) = @@ -35,7 +39,7 @@ class EnergyBar( val energyPlus = getEnergyPlus() val energyMinus = getEnergyMinus() - val builder = createItemBuilder(DefaultGuiItems.BAR_RED, section, energy.toDouble() / maxEnergy.toDouble()) + val builder = createItemBuilder(item, section, energy.toDouble() / maxEnergy.toDouble()) if (energy == Long.MAX_VALUE) { builder.setName("∞ J / ∞ J") diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/FluidBar.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/FluidBar.kt index 33ad6f135b6..034f8b4371d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/FluidBar.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/FluidBar.kt @@ -17,10 +17,7 @@ import xyz.xenondevs.nova.world.block.tileentity.network.type.fluid.holder.Fluid import xyz.xenondevs.nova.world.item.DefaultGuiItems import xyz.xenondevs.nova.world.item.NovaItem -private fun getFluidBarItem(type: FluidType?): NovaItem = when (type) { - FluidType.WATER -> DefaultGuiItems.BAR_BLUE - else -> DefaultGuiItems.BAR_ORANGE -} +private val DEFAULT_FLUID_BAR_ITEMS = mapOf(null to DefaultGuiItems.BAR_BLUE, FluidType.WATER to DefaultGuiItems.BAR_BLUE, FluidType.LAVA to DefaultGuiItems.BAR_ORANGE) private fun ItemBuilder.setFluidDisplayName(amount: Long, capacity: Long): ItemBuilder { if (amount == Long.MAX_VALUE) { @@ -37,26 +34,39 @@ private fun ItemBuilder.setFluidDisplayName(amount: Long, capacity: Long): ItemB /** * A multi-item gui component for displaying fluid levels. */ -class FluidBar( +class FluidBar @JvmOverloads constructor( // TODO: Remove @JvmOverloads in 0.19 height: Int, fluidHolder: FluidHolder, private val fluidContainer: NetworkedFluidContainer, private val capacity: Provider, private val type: Provider, - private val amount: Provider + private val amount: Provider, + private val items: Map = DEFAULT_FLUID_BAR_ITEMS ) : VerticalBar(height) { private val allowedConnectionType = fluidHolder.containers[fluidContainer]!! - constructor(height: Int, fluidHolder: FluidHolder, container: FluidContainer) : this( - height, fluidHolder, container, container.capacityProvider, container.typeProvider, container.amountProvider + @JvmOverloads + constructor( + height: Int, + fluidHolder: FluidHolder, + container: FluidContainer, + items: Map = DEFAULT_FLUID_BAR_ITEMS + ) : this( + height, + fluidHolder, + container, + container.capacityProvider, + container.typeProvider, + container.amountProvider, + items ) @Suppress("DEPRECATION") override fun createBarItem(section: Int) = Item.builder() .setItemProvider(type, amount, capacity) { type, amount, capacity -> createItemBuilder( - getFluidBarItem(type), + items[type]!!, section, amount.toDouble() / capacity.toDouble() ).setFluidDisplayName(amount, capacity) @@ -97,13 +107,14 @@ class StaticFluidBar( height: Int, private val capacity: Long, private val type: FluidType, - private val amount: Long + private val amount: Long, + private val items: Map = DEFAULT_FLUID_BAR_ITEMS ) : VerticalBar(height) { override fun createBarItem(section: Int): Item { return Item.simple( createItemBuilder( - getFluidBarItem(type), + items[type]!!, section, amount.toDouble() / capacity.toDouble() ).setFluidDisplayName(amount, capacity) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/StructureIngredients.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/StructureIngredients.kt index 509413ae286..2cd7988bdab 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/StructureIngredients.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/StructureIngredients.kt @@ -3,6 +3,7 @@ package xyz.xenondevs.nova.ui.menu import xyz.xenondevs.invui.gui.Gui import xyz.xenondevs.invui.gui.IngredientMapper import xyz.xenondevs.invui.gui.Markers +import xyz.xenondevs.invui.gui.Structure import xyz.xenondevs.invui.gui.Structure.addGlobalIngredient import xyz.xenondevs.invui.inventory.Inventory import xyz.xenondevs.nova.ui.menu.item.PageBackItem @@ -29,6 +30,7 @@ internal fun setGlobalIngredients() { addGlobalIngredient('d', ::ScrollDownItem) addGlobalIngredient('<', ::PageBackItem) addGlobalIngredient('>', ::PageForwardItem) + Structure.freezeGlobalIngredients() } fun > IngredientMapper.addIngredient(char: Char, item: NovaItem): S = diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsMenu.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsMenu.kt new file mode 100644 index 00000000000..b1098726fb5 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsMenu.kt @@ -0,0 +1,275 @@ +package xyz.xenondevs.nova.ui.menu.explorer.creative + +import me.xdrop.fuzzywuzzy.FuzzySearch +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import org.bukkit.persistence.PersistentDataType +import xyz.xenondevs.commons.provider.MutableProvider +import xyz.xenondevs.commons.provider.Provider +import xyz.xenondevs.commons.provider.combinedProvider +import xyz.xenondevs.commons.provider.flatten +import xyz.xenondevs.commons.provider.map +import xyz.xenondevs.commons.provider.mapEach +import xyz.xenondevs.commons.provider.mutableProvider +import xyz.xenondevs.commons.provider.provider +import xyz.xenondevs.invui.gui.Gui +import xyz.xenondevs.invui.gui.PagedGui +import xyz.xenondevs.invui.gui.ScrollGui +import xyz.xenondevs.invui.gui.TabGui +import xyz.xenondevs.invui.gui.setContent +import xyz.xenondevs.invui.gui.setTab +import xyz.xenondevs.invui.gui.setTabs +import xyz.xenondevs.invui.item.AbstractPagedGuiBoundItem +import xyz.xenondevs.invui.item.AbstractTabGuiBoundItem +import xyz.xenondevs.invui.item.Click +import xyz.xenondevs.invui.item.Item +import xyz.xenondevs.invui.item.ItemProvider +import xyz.xenondevs.invui.item.setItemProvider +import xyz.xenondevs.invui.window.AnvilWindow +import xyz.xenondevs.invui.window.Window +import xyz.xenondevs.invui.window.addRenameHandler +import xyz.xenondevs.invui.window.setTitle +import xyz.xenondevs.nova.ui.menu.applyDefaultTPIngredients +import xyz.xenondevs.nova.ui.menu.explorer.ItemMenu +import xyz.xenondevs.nova.ui.menu.item.AnvilTextItem +import xyz.xenondevs.nova.ui.overlay.guitexture.DefaultGuiTextures +import xyz.xenondevs.nova.util.component.adventure.toPlainText +import xyz.xenondevs.nova.util.playClickSound +import xyz.xenondevs.nova.world.item.DefaultGuiItems +import xyz.xenondevs.nova.world.item.ItemCategories +import xyz.xenondevs.nova.world.item.ItemCategory + +private val TAB_BUTTON_TEXTURES = arrayOf( + DefaultGuiTextures.ITEMS_0, + DefaultGuiTextures.ITEMS_1, + DefaultGuiTextures.ITEMS_2, + DefaultGuiTextures.ITEMS_3, + DefaultGuiTextures.ITEMS_4 +) + +private const val GIVE_PERMISSION = "nova.command.give" + +internal class ItemsMenu(val player: Player) : ItemMenu { + + private val cheatMode: MutableProvider = mutableProvider { player.persistentDataContainer.get(CHEAT_MODE_KEY, PersistentDataType.BOOLEAN) == true } + .apply { + subscribe { cheatMode -> + player.persistentDataContainer.set(CHEAT_MODE_KEY, PersistentDataType.BOOLEAN, cheatMode) + } + } + private val activeTab: MutableProvider = mutableProvider(0) + private val filter: MutableProvider = mutableProvider("") + + private val filteredItems: Provider> = combinedProvider(filter, ItemCategories.obtainableItems) { filter, obtainableItems -> + if (filter.isNotEmpty()) { + val names = obtainableItems + .asSequence() + .map { it to it.name.toPlainText(player) } + .filter { (_, name) -> name.contains(filter, true) } + .toMap(HashMap()) + val scores = FuzzySearch.extractAll(filter, names.values).associateTo(HashMap()) { it.string to it.score } + names.keys.sortedWith { o1, o2 -> + val s1 = scores[names[o1]]!! + val s2 = scores[names[o2]]!! + if (s1 == s2) { + val i1 = obtainableItems.indexOf(o1) + val i2 = obtainableItems.indexOf(o2) + i1.compareTo(i2) + } else s1.compareTo(s2) + } + } else obtainableItems.toList() + } + + private val tabButtons: MutableProvider>> = mutableProvider(provider(emptyList())) + + private val openSearchItem: Item = Item.builder() + .setItemProvider(DefaultGuiItems.TP_SEARCH.clientsideProvider) + .addClickHandler { _, click -> + if (click.clickType == ClickType.LEFT) { + searchWindow.open() + click.player.playClickSound() + } + }.build() + + private val cheatModeItem: Item = Item.builder() + .setItemProvider(cheatMode) { cheatMode -> + if (cheatMode) + DefaultGuiItems.TP_CHEATING_ON.clientsideProvider + else DefaultGuiItems.TP_CHEATING_OFF.clientsideProvider + }.addClickHandler { _, click -> + if (click.clickType == ClickType.LEFT && player.hasPermission(GIVE_PERMISSION)) { + cheatMode.set(!cheatMode.get()) + click.player.playClickSound() + } + }.build() + + private val mainWindow = Window.single() + .setTitle(activeTab) { tab -> TAB_BUTTON_TEXTURES[tab % 5].component } + .setGui(TabGui.normal() + .setStructure( + "p p p p p p p p p", + "p p p p p p p p p", + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x" + ) + .addIngredient('p', PagedGui.items() + .setStructure( + "x . x . x . x . x", + "< . . . . . . . >" + ) + .addIngredient('<', TabPageBackItem()) + .addIngredient('>', TabPageForwardItem()) + .addPageChangeHandler { _, page -> activeTab.set(page * 5) } + .setContent(tabButtons.flatten()) + .build() + ) + .setTab(activeTab) + .setTabs(ItemCategories.categories.mapEach { category -> + ScrollGui.items() + .setStructure( + "x x x x x x x x s", + "x x x x x x x x c", + "x x x x x x x x u", + "x x x x x x x x d" + ) + .applyDefaultTPIngredients() + .addIngredient('s', openSearchItem) + .addIngredient('c', cheatModeItem) + .setContent(category.items) + .build() + }) + .addModifier { tabGui -> + // because the tab gui buttons are placed in a paged gui, they need to be bound manually + val buttons = ItemCategories.categories.map { categories -> + categories + .mapIndexed { i, category -> CreativeTabItem(i, category) } + .onEach { it.bind(tabGui) } + } + tabButtons.set(buttons) + } + ) + .build(player) + + private val searchWindow = AnvilWindow.split() + .setTitle(DefaultGuiTextures.SEARCH.getTitle("menu.nova.items.search")) + .setUpperGui(Gui.normal() + .setStructure("a . .") + .addIngredient('a', AnvilTextItem(DefaultGuiItems.INVISIBLE_ITEM.createClientsideItemBuilder(), "")) + ) + .setLowerGui(PagedGui.items() + .setStructure( + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x", + "# # # < # > # # s" + ) + .addIngredient('s', Item.builder() + .setItemProvider( + DefaultGuiItems.ARROW_UP_ON.createClientsideItemBuilder() + .setName(Component.translatable("menu.nova.items.search.back", NamedTextColor.GRAY)) + ) + .addClickHandler { _, click -> + if (click.clickType == ClickType.LEFT) { + if (filter.get().isBlank()) mainWindow.open() else searchResultsWindow.open() + click.player.playClickSound() + } + } + ) + .setContent(filteredItems) + .build() + ) + .addRenameHandler(filter) + .build(player) + + private val searchResultsWindow = Window.single() + .setTitle(filter) { filter -> + val title = Component.text() + .append(Component.translatable("menu.nova.items")) + .append(Component.text(" (", NamedTextColor.DARK_GRAY)) + .append(Component.text(filter, NamedTextColor.GRAY)) + .append(Component.text(")", NamedTextColor.DARK_GRAY)) + .build() + DefaultGuiTextures.EMPTY_GUI.getTitle(title) + } + .setGui(PagedGui.items() + .setStructure( + ". . . < s > . . .", + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x", + "x x x x x x x x x" + ) + .applyDefaultTPIngredients() + .addIngredient('s', openSearchItem) + .setContent(filteredItems) + .build() + ) + .build(player) + + override fun show() { + ItemMenu.addToHistory(player.uniqueId, this) + mainWindow.open() + } + + private inner class CreativeTabItem(private val tab: Int, private val category: ItemCategory) : AbstractTabGuiBoundItem() { + + override fun getItemProvider(player: Player) = category.icon + + override fun handleClick(clickType: ClickType, player: Player, click: Click) { + if (clickType == ClickType.LEFT && gui.isTabAvailable(tab) && gui.tab != tab) { + player.playClickSound() + gui.tab = tab + } + } + + } + + private class TabPageBackItem : AbstractPagedGuiBoundItem() { + + override fun getItemProvider(player: Player): ItemProvider { + return if (gui.pageAmount <= 1) + ItemProvider.EMPTY + else if (gui.hasPreviousPage()) + DefaultGuiItems.TP_SMALL_ARROW_LEFT_ON.clientsideProvider + else DefaultGuiItems.TP_SMALL_ARROW_LEFT_OFF.clientsideProvider + } + + override fun handleClick(clickType: ClickType, player: Player, click: Click) { + if (clickType == ClickType.LEFT) { + gui.page-- + player.playClickSound() + } + } + + } + + private class TabPageForwardItem : AbstractPagedGuiBoundItem() { + + override fun getItemProvider(player: Player): ItemProvider { + return if (gui.pageAmount <= 1) + ItemProvider.EMPTY + else if (gui.hasNextPage()) + DefaultGuiItems.TP_SMALL_ARROW_RIGHT_ON.clientsideProvider + else DefaultGuiItems.TP_SMALL_ARROW_RIGHT_OFF.clientsideProvider + } + + override fun handleClick(clickType: ClickType, player: Player, click: Click) { + if (clickType == ClickType.LEFT) { + gui.page++ + player.playClickSound() + } + } + + } + + companion object { + val CHEAT_MODE_KEY = NamespacedKey("nova", "cheat_mode") + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsWindow.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsWindow.kt deleted file mode 100644 index af4e6947f53..00000000000 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/explorer/creative/ItemsWindow.kt +++ /dev/null @@ -1,307 +0,0 @@ -package xyz.xenondevs.nova.ui.menu.explorer.creative - -import me.xdrop.fuzzywuzzy.FuzzySearch -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import org.bukkit.NamespacedKey -import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType -import org.bukkit.persistence.PersistentDataType -import xyz.xenondevs.commons.collections.takeUnlessEmpty -import xyz.xenondevs.commons.collections.weakHashSet -import xyz.xenondevs.invui.gui.Gui -import xyz.xenondevs.invui.gui.PagedGui -import xyz.xenondevs.invui.gui.ScrollGui -import xyz.xenondevs.invui.gui.TabGui -import xyz.xenondevs.invui.item.AbstractPagedGuiBoundItem -import xyz.xenondevs.invui.item.AbstractTabGuiBoundItem -import xyz.xenondevs.invui.item.Click -import xyz.xenondevs.invui.item.Item -import xyz.xenondevs.invui.item.ItemProvider -import xyz.xenondevs.invui.window.AnvilWindow -import xyz.xenondevs.invui.window.Window -import xyz.xenondevs.nova.ui.menu.applyDefaultTPIngredients -import xyz.xenondevs.nova.ui.menu.explorer.ItemMenu -import xyz.xenondevs.nova.ui.menu.item.AnvilTextItem -import xyz.xenondevs.nova.ui.menu.item.ToggleItem -import xyz.xenondevs.nova.ui.overlay.guitexture.DefaultGuiTextures -import xyz.xenondevs.nova.util.component.adventure.moveToStart -import xyz.xenondevs.nova.util.component.adventure.toPlainText -import xyz.xenondevs.nova.util.playClickSound -import xyz.xenondevs.nova.world.item.DefaultGuiItems -import xyz.xenondevs.nova.world.item.ItemCategories -import xyz.xenondevs.nova.world.item.ItemCategories.OBTAINABLE_ITEMS -import xyz.xenondevs.nova.world.item.ItemCategory - -private val TAB_BUTTON_TEXTURES = arrayOf( - DefaultGuiTextures.ITEMS_0, - DefaultGuiTextures.ITEMS_1, - DefaultGuiTextures.ITEMS_2, - DefaultGuiTextures.ITEMS_3, - DefaultGuiTextures.ITEMS_4 -) - -private const val GIVE_PERMISSION = "nova.command.give" - -internal class ItemsWindow(val player: Player) : ItemMenu { - - private var currentWindow: Window? = null - - private val openSearchItem = Item.builder() - .setItemProvider(DefaultGuiItems.TP_SEARCH.clientsideProvider) - .addClickHandler { _, _ -> openSearchWindow() } - .build() - - private val toggleCheatModeItem = ToggleItem( - player in cheaters, - DefaultGuiItems.TP_CHEATING_ON.clientsideProvider, - DefaultGuiItems.TP_CHEATING_OFF.clientsideProvider, - ) { - if (player.hasPermission(GIVE_PERMISSION)) { - player.persistentDataContainer.set(CHEAT_MODE_KEY, PersistentDataType.BOOLEAN, it) - if (it) cheaters += player else cheaters -= player - return@ToggleItem true - } - return@ToggleItem false - } - - private val openMainWindowItem = Item.builder() - .setItemProvider( - DefaultGuiItems.ARROW_UP_ON.createClientsideItemBuilder() - .setName(Component.translatable("menu.nova.items.search.back", NamedTextColor.GRAY)) - ).addClickHandler { _, _ -> openMainWindow() } - - private val tabPagesGui = PagedGui.items() - .setStructure( - "x . x . x . x . x", - "< . . . . . . . >" - ) - .addIngredient('<', TabPageBackItem()) - .addIngredient('>', TabPageForwardItem()) - .addPageChangeHandler { _, now -> handleTabPageChange(now) } - .build() - - private val mainGui = TabGui.normal() - .setStructure( - ". . . . . . . . .", - ". . . . . . . . .", - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x" - ) - .addIngredient('s', openSearchItem) - .setTabs(ItemCategories.CATEGORIES.map(::createCategoryGui).takeUnlessEmpty() ?: listOf(Gui.empty(9, 4))) - .addModifier { it.fillRectangle(0, 0, tabPagesGui, true) } - .build() - - private val searchResultsGui = PagedGui.items() - .setStructure( - ". . . < s > . . .", - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x" - ) - .applyDefaultTPIngredients() - .addIngredient('s', openSearchItem) - .build() - - private val searchPreviewGui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # < # > # # s" - ) - .addIngredient('s', openMainWindowItem) - .build() - - private val textItem = AnvilTextItem(DefaultGuiItems.INVISIBLE_ITEM.createClientsideItemBuilder(), "") - - private var filteredItems: List? = null - private var filter = "" - set(value) { - if (field != value && value != ".") { - field = value - updateFilteredItems() - } - } - - init { - val tabButtons = ItemCategories.CATEGORIES - .withIndex() - .map { (index, category) -> CreativeTabItem(index, category).apply { bind(mainGui) } } - tabPagesGui.content = tabButtons - - updateFilteredItems() - if (player.hasPermission(GIVE_PERMISSION)) { - if (player !in cheaters && player.persistentDataContainer.get(CHEAT_MODE_KEY, PersistentDataType.BOOLEAN) == true) { - cheaters += player - toggleCheatModeItem.state = true - toggleCheatModeItem.notifyWindows() - } - } else { - if (player in cheaters) { - cheaters -= player - toggleCheatModeItem.state = false - toggleCheatModeItem.notifyWindows() - } - } - } - - private fun handleTabPageChange(newTab: Int) { - mainGui.tab = newTab * 5 - currentWindow?.setTitle(getMainWindowTitle()) - } - - private fun updateFilteredItems() { - filteredItems = if (filter.isNotEmpty()) { - val names = OBTAINABLE_ITEMS - .asSequence() - .map { it to it.name.toPlainText(player) } - .filter { (_, name) -> name.contains(filter, true) } - .toMap(HashMap()) - val scores = FuzzySearch.extractAll(filter, names.values).associateTo(HashMap()) { it.string to it.score } - names.keys.sortedWith { o1, o2 -> - val s1 = scores[names[o1]]!! - val s2 = scores[names[o2]]!! - if (s1 == s2) { - val i1 = OBTAINABLE_ITEMS.indexOf(o1) - val i2 = OBTAINABLE_ITEMS.indexOf(o2) - i1.compareTo(i2) - } else s1.compareTo(s2) - } - } else OBTAINABLE_ITEMS.toList() - - searchResultsGui.content = filteredItems ?: emptyList() - searchPreviewGui.content = filteredItems ?: emptyList() - } - - private fun getMainWindowTitle(): Component { - return if (filter == "") { - Component.text() - .append(TAB_BUTTON_TEXTURES[mainGui.tab % 5].component) - .build() - } else { - val title = Component.text() - .append(Component.translatable("menu.nova.items")) - .append(Component.text(" (", NamedTextColor.DARK_GRAY)) - .append(Component.text(filter, NamedTextColor.GRAY)) - .append(Component.text(")", NamedTextColor.DARK_GRAY)) - .build() - - DefaultGuiTextures.EMPTY_GUI.getTitle(title) - } - } - - private fun openMainWindow() { - currentWindow = Window.single { - it.setViewer(player) - it.setTitle(getMainWindowTitle()) - it.setGui(if (filter == "") mainGui else searchResultsGui) - }.apply { open() } - } - - private fun openSearchWindow() { - filter = "" - - val anvilGui = Gui.empty(3, 1).apply { - setItem(0, textItem) - } - - val title = Component.text() - .append(DefaultGuiTextures.SEARCH.component) - .moveToStart() - .append(Component.translatable("menu.nova.items.search", NamedTextColor.DARK_GRAY)) - .build() - - currentWindow = AnvilWindow.split { - it.setViewer(player) - it.setTitle(title) - it.setUpperGui(anvilGui) - it.setLowerGui(searchPreviewGui) - it.addRenameHandler { text -> filter = text } - }.apply { open() } - } - - override fun show() { - ItemMenu.addToHistory(player.uniqueId, this) - openMainWindow() - } - - private fun createCategoryGui(category: ItemCategory): Gui { - return ScrollGui.items() - .setStructure( - "x x x x x x x x s", - "x x x x x x x x c", - "x x x x x x x x u", - "x x x x x x x x d" - ) - .applyDefaultTPIngredients() - .addIngredient('s', openSearchItem) - .addIngredient('c', toggleCheatModeItem) - .setContent(category.items) - .build() - } - - private inner class CreativeTabItem(private val tab: Int, private val category: ItemCategory) : AbstractTabGuiBoundItem() { - - override fun getItemProvider(player: Player) = category.icon - - override fun handleClick(clickType: ClickType, player: Player, click: Click) { - if (clickType == ClickType.LEFT && gui.isTabAvailable(tab) && gui.tab != tab) { - player.playClickSound() - gui.tab = tab - - currentWindow?.setTitle(getMainWindowTitle()) - } - } - - } - - private class TabPageBackItem : AbstractPagedGuiBoundItem() { - - override fun getItemProvider(player: Player): ItemProvider { - return if (gui.pageAmount <= 1) - ItemProvider.EMPTY - else if (gui.hasPreviousPage()) - DefaultGuiItems.TP_SMALL_ARROW_LEFT_ON.clientsideProvider - else DefaultGuiItems.TP_SMALL_ARROW_LEFT_OFF.clientsideProvider - } - - override fun handleClick(clickType: ClickType, player: Player, click: Click) { - if (clickType == ClickType.LEFT) { - gui.page-- - player.playClickSound() - } - } - - } - - private class TabPageForwardItem : AbstractPagedGuiBoundItem() { - - override fun getItemProvider(player: Player): ItemProvider { - return if (gui.pageAmount <= 1) - ItemProvider.EMPTY - else if (gui.hasNextPage()) - DefaultGuiItems.TP_SMALL_ARROW_RIGHT_ON.clientsideProvider - else DefaultGuiItems.TP_SMALL_ARROW_RIGHT_OFF.clientsideProvider - } - - override fun handleClick(clickType: ClickType, player: Player, click: Click) { - if (clickType == ClickType.LEFT) { - gui.page++ - player.playClickSound() - } - } - - } - - companion object { - val CHEAT_MODE_KEY = NamespacedKey("nova", "cheat_mode") - val cheaters = weakHashSet() - } - -} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/sideconfig/SideConfigMenu.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/sideconfig/SideConfigMenu.kt index 730cd514fba..f325118cf22 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/sideconfig/SideConfigMenu.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/menu/sideconfig/SideConfigMenu.kt @@ -13,6 +13,7 @@ import xyz.xenondevs.invui.window.Window import xyz.xenondevs.nova.ui.menu.item.BackItem import xyz.xenondevs.nova.ui.menu.item.ClickyTabItem import xyz.xenondevs.nova.util.playClickSound +import xyz.xenondevs.nova.world.block.tileentity.TileEntity import xyz.xenondevs.nova.world.block.tileentity.network.NetworkManager import xyz.xenondevs.nova.world.block.tileentity.network.node.NetworkEndPoint import xyz.xenondevs.nova.world.block.tileentity.network.type.energy.holder.EnergyHolder @@ -168,12 +169,18 @@ class SideConfigMenu( * Opens a [Window] of this [SideConfigMenu] for the given [player]. */ fun openWindow(player: Player) { - Window.single { + val window = Window.single { it.setViewer(player) it.setTitle(Component.translatable("menu.nova.side_config")) it.setGui(mainGui) it.addOpenHandler(::updateNetworkData) - }.open() + } + + if (endPoint is TileEntity) { + endPoint.menuContainer.registerWindow(window) + } + + window.open() } private fun updateNetworkData() { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/MovedFonts.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/MovedFonts.kt index 118e0ed6af9..c1304f2b762 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/MovedFonts.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/MovedFonts.kt @@ -3,13 +3,9 @@ package xyz.xenondevs.nova.ui.overlay import net.kyori.adventure.text.BuildableComponent import net.kyori.adventure.text.Component import net.kyori.adventure.text.ComponentBuilder -import xyz.xenondevs.nova.config.MAIN_CONFIG -import xyz.xenondevs.nova.config.entry import xyz.xenondevs.nova.util.component.adventure.font import xyz.xenondevs.nova.util.component.adventure.fontName -private val FORCE_UNIFORM_FONT by MAIN_CONFIG.entry("resource_pack", "force_uniform_font") - object MovedFonts { private val MOVED_FONT_REGEX = Regex("""([a-z0-9/._:-]*)/([\d-]*)""") @@ -47,9 +43,6 @@ object MovedFonts { } } - if (FORCE_UNIFORM_FONT && (font == "default" || font == "minecraft:default")) - font = "uniform" - val newDistance = if (addDistance) currentDistance + distance else distance if (newDistance != 0) { builder.font("$font/${newDistance}") diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/BossBarOverlayManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/BossBarOverlayManager.kt index 25fbd9d6eaa..d5dc181c6dc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/BossBarOverlayManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/BossBarOverlayManager.kt @@ -1,5 +1,6 @@ package xyz.xenondevs.nova.ui.overlay.bossbar +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader import net.kyori.adventure.text.Component import net.minecraft.world.BossEvent import org.bukkit.Bukkit @@ -12,7 +13,6 @@ import org.bukkit.event.player.PlayerResourcePackStatusEvent import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status import org.bukkit.plugin.Plugin import org.bukkit.scheduler.BukkitTask -import xyz.xenondevs.invui.internal.util.ReflectionRegistry import xyz.xenondevs.nova.Nova import xyz.xenondevs.nova.config.MAIN_CONFIG import xyz.xenondevs.nova.config.entry @@ -352,17 +352,18 @@ object BossBarOverlayManager : Listener, PacketListener { } } + @Suppress("UnstableApiUsage") internal fun handleBossBarAddPacketCreation(event: BossEvent) { var plugin: Plugin? = null StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).forEach { val classLoader = it.declaringClass.classLoader - if (classLoader?.javaClass == ReflectionRegistry.PLUGIN_CLASS_LOADER_CLASS) { // TODO: paper plugin class loader - plugin = ReflectionRegistry.PLUGIN_CLASS_LOADER_PLUGIN_FIELD.get(classLoader) as Plugin + if (classLoader is ConfiguredPluginClassLoader) { + plugin = classLoader.plugin } } if (plugin != null && plugin != Nova) { - trackedOrigins[event.id] = BarOrigin.Plugin(plugin) + trackedOrigins[event.id] = BarOrigin.Plugin(plugin!!) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/vanilla/VanillaBossBarOverlay.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/vanilla/VanillaBossBarOverlay.kt index 8d8ff779838..bbb7da07f97 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/vanilla/VanillaBossBarOverlay.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/bossbar/vanilla/VanillaBossBarOverlay.kt @@ -1,6 +1,7 @@ package xyz.xenondevs.nova.ui.overlay.bossbar.vanilla import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.ShadowColor import net.kyori.adventure.text.format.Style import net.kyori.adventure.text.format.TextColor import net.minecraft.world.BossEvent.BossBarColor @@ -65,7 +66,7 @@ internal class VanillaBossBarOverlay( builder .move(-HALF_BOSS_BAR_LENGTH) - .append(Component.text('\uF000').font(BOSS_BAR_FONT)) // background + .append(Component.text('\uF000').font(BOSS_BAR_FONT).shadowColor(ShadowColor.none())) // background .move(-BOSS_BAR_LENGTH - 1) val progress = getProgressComponent(bar.progress, color) @@ -105,7 +106,7 @@ internal class VanillaBossBarOverlay( val i = (progress * BOSS_BAR_LENGTH).roundToInt() val char = (0xFF00 + i).toChar().toString() - val component = Component.text(char, Style.style(color)).font(BOSS_BAR_FONT) + val component = Component.text(char, Style.style(color)).font(BOSS_BAR_FONT).shadowColor(ShadowColor.none()) return component to i } @@ -118,7 +119,7 @@ internal class VanillaBossBarOverlay( BossBarStyle.NOTCHED_20 -> "\uF004" } ?: return null - return Component.text(char).font(BOSS_BAR_FONT) + return Component.text(char).font(BOSS_BAR_FONT).shadowColor(ShadowColor.none()) } override fun toString(): String { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/guitexture/DefaultGuiTextures.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/guitexture/DefaultGuiTextures.kt index 6ff0deca8df..08b6a087800 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/guitexture/DefaultGuiTextures.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/overlay/guitexture/DefaultGuiTextures.kt @@ -45,6 +45,7 @@ object DefaultGuiTextures { val RECIPE_CONVERSION = guiTexture("recipe_conversion") { path("gui/recipe/conversion") } + @Deprecated("Color picker will be removed in a future version") val COLOR_PICKER = guiTexture("color_picker") { path("gui/color_picker") } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/waila/overlay/WailaImageOverlay.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/waila/overlay/WailaImageOverlay.kt index bc19933f123..0962f056719 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/ui/waila/overlay/WailaImageOverlay.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/ui/waila/overlay/WailaImageOverlay.kt @@ -3,6 +3,7 @@ package xyz.xenondevs.nova.ui.waila.overlay import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.ShadowColor import xyz.xenondevs.nova.resources.builder.task.font.FontChar import xyz.xenondevs.nova.ui.overlay.bossbar.BossBarOverlay import xyz.xenondevs.nova.util.component.adventure.append @@ -122,7 +123,7 @@ internal class WailaImageOverlay : BossBarOverlay { * Valid [types][type] are 0: start, 1: part, 2: end. */ private fun getComponent(lines: Int, type: Int): Component { - return Component.text(getChar(lines, type)).font(WAILA_FONT) + return Component.text(getChar(lines, type)).font(WAILA_FONT).shadowColor(ShadowColor.none()) } } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt index 72edfffa64c..07fe7d1976c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt @@ -65,6 +65,7 @@ import xyz.xenondevs.nova.world.block.logic.sound.SoundEngine import xyz.xenondevs.nova.world.block.sound.SoundGroup import xyz.xenondevs.nova.world.block.state.NovaBlockState import xyz.xenondevs.nova.world.block.state.model.BackingStateBlockModelProvider +import xyz.xenondevs.nova.world.block.state.model.ModelLessBlockModelProvider import xyz.xenondevs.nova.world.format.WorldDataManager import xyz.xenondevs.nova.world.pos import java.util.* @@ -408,7 +409,7 @@ object BlockUtils { if (CustomItemServiceManager.getId(bukkitBlock) != null) { val itemDrops = if (drops) - CustomItemServiceManager.getDrops(bukkitBlock, ctx[DefaultContextParamTypes.TOOL_ITEM_STACK])!! + CustomItemServiceManager.getDrops(bukkitBlock, ctx[DefaultContextParamTypes.TOOL_ITEM_STACK]) ?: emptyList() else emptyList() CustomItemServiceManager.removeBlock(bukkitBlock, breakEffects) return itemDrops @@ -416,9 +417,7 @@ object BlockUtils { val novaBlockState = WorldDataManager.getBlockState(pos) if (novaBlockState != null) { - val itemDrops = if (drops) - novaBlockState.block.getDrops(pos, novaBlockState, ctx) - else emptyList() + val itemDrops = novaBlockState.block.getDrops(pos, novaBlockState, ctx) breakNovaBlockInternal(ctx, sendEffectsToBreaker) return itemDrops } @@ -475,7 +474,8 @@ object BlockUtils { } val soundGroup = state.block.getBehaviorOrNull()?.soundGroup - if (state.modelProvider.provider == BackingStateBlockModelProvider) { + val modelProvider = state.modelProvider.provider + if (modelProvider == BackingStateBlockModelProvider || modelProvider == ModelLessBlockModelProvider) { // use the level event packet for blocks that use block states val levelEventPacket = ClientboundLevelEventPacket(2001, nmsPos, pos.nmsBlockState.id, false) broadcast(levelEventPacket, sendEffectsToBreaker) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/TickResettingLong.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/TickResettingLong.kt new file mode 100644 index 00000000000..cc00d6f0ca2 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/TickResettingLong.kt @@ -0,0 +1,36 @@ +package xyz.xenondevs.nova.util + +import kotlin.reflect.KProperty + +internal class TickResettingLong { + + private var accumulatorTick = 0 + + private var value = 0L + private var accumulator = 0L + + operator fun getValue(thisRef: Any?, property: KProperty<*>): Long { + checkCompleteAccumulation() + return value + } + + fun add(value: Long) { + checkCompleteAccumulation() + accumulator += value + } + + private fun checkCompleteAccumulation() { + if (serverTick > accumulatorTick) { + if (serverTick == (accumulatorTick + 1)) { + value = accumulator + accumulator = 0 + } else { + value = 0 + accumulator = 0 + } + + accumulatorTick = serverTick + } + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/TimedResettingLong.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/TimedResettingLong.kt deleted file mode 100644 index 5edd20046f0..00000000000 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/TimedResettingLong.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.xenondevs.nova.util - -import xyz.xenondevs.commons.provider.Provider -import kotlin.reflect.KProperty - -internal class TimedResettingLong(resetDelay: Provider) { - - private val resetDelay by resetDelay - private var lastReset = 0 - - private var value = 0L - - operator fun getValue(thisRef: Any?, property: KProperty<*>): Long { - if ((serverTick - lastReset) / resetDelay > 0) { - lastReset = serverTick - value = 0 - } - - return value - } - - fun add(value: Long) { - if ((serverTick - lastReset) / resetDelay > 0) { - lastReset = serverTick - this.value = value - } else { - this.value += value - } - } - -} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/advancement/AdvancementUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/advancement/AdvancementUtils.kt index 4f1470debad..4b55eddfcbf 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/advancement/AdvancementUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/advancement/AdvancementUtils.kt @@ -1,19 +1,10 @@ package xyz.xenondevs.nova.util.advancement import net.kyori.adventure.text.Component -import net.minecraft.advancements.Advancement -import net.minecraft.advancements.AdvancementHolder -import net.minecraft.advancements.AdvancementRequirements -import net.minecraft.advancements.AdvancementType -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.DisplayInfo -import net.minecraft.advancements.critereon.InventoryChangeTrigger -import net.minecraft.advancements.critereon.ItemPredicate -import net.minecraft.core.component.DataComponentPredicate -import net.minecraft.core.component.DataComponents +import net.minecraft.advancements.* +import net.minecraft.advancements.critereon.* import net.minecraft.nbt.CompoundTag import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.component.CustomData import org.bukkit.Bukkit import org.bukkit.NamespacedKey import org.bukkit.entity.Player @@ -94,16 +85,17 @@ fun obtainNovaItemsAdvancement( } private fun createObtainNovaItemCriterion(item: NovaItem): Criterion { - val expectedCustomData = CustomData.of(CompoundTag().apply { + val expectedCustomData = CompoundTag().apply { put("nova", CompoundTag().apply { putString("id", item.id.toString()) }) - }) + } return InventoryChangeTrigger.TriggerInstance.hasItems( - ItemPredicate.Builder.item().hasComponents( - DataComponentPredicate.builder() - .expect(DataComponents.CUSTOM_DATA, expectedCustomData) - .build() + ItemPredicate.Builder.item().withSubPredicate( + ItemSubPredicates.CUSTOM_DATA, + ItemCustomDataPredicate.customData( + NbtPredicate(expectedCustomData) + ) ) ) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/IOUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/IOUtils.kt index 9adac171cb0..0e2227a53cd 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/IOUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/IOUtils.kt @@ -65,7 +65,7 @@ internal inline fun Path.useZip(create: Boolean = false, run: (Path) -> T): } /** - * Decodes [value] from JSON via [json] as [T] from the file. + * Decodes the contents of [this] JSON file via [json] as [T] from the file. */ @PublishedApi @OptIn(ExperimentalSerializationApi::class) @@ -73,6 +73,14 @@ internal inline fun Path.readJson(json: Json = Json): T { return inputStream().use { json.decodeFromStream(it) } } +/** + * Decodes the contents of [this] JSON file as [T] via a custom [Json] instance only configured by [ignoreUnknownKeys]. + */ +@PublishedApi +internal inline fun Path.readJson(ignoreUnknownKeys: Boolean): T { + return readJson(Json { this.ignoreUnknownKeys = ignoreUnknownKeys }) +} + /** * Encodes [value] to JSON via [json] as [T] and writes it to the file. */ diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/ImageUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/ImageUtils.kt index 5753813e7e6..b951cce523e 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/ImageUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/ImageUtils.kt @@ -120,7 +120,7 @@ internal object ImageUtils { @JvmStatic fun createImageFromArgbRaster(width: Int, raster: IntArray): BufferedImage { // https://stackoverflow.com/questions/14416107/int-array-to-bufferedimage - val sm = SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, width, 16, ARGB_BIT_MASKS) + val sm = SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, width, raster.size / width, ARGB_BIT_MASKS) val db = DataBufferInt(raster, raster.size) val wr = Raster.createWritableRaster(sm, db, Point()) return BufferedImage(ColorModel.getRGBdefault(), wr, false, null) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/http/BinaryBufferedBody.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/http/BinaryBufferedBody.kt index 53ce9751394..71ec8eb8530 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/http/BinaryBufferedBody.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/data/http/BinaryBufferedBody.kt @@ -11,7 +11,7 @@ internal class BinaryBufferedBody(val stream: InputStream, override val contentT override suspend fun writeTo(channel: ByteWriteChannel) { val buffer = ByteArray(8192) var len = 0 - while ({ len = stream.read(buffer); len }() != 0) { + while ({ len = stream.read(buffer); len }() != -1) { channel.writeFully(buffer, 0, len) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/ToolUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/ToolUtils.kt index 20908f57cac..634f600c61a 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/ToolUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/ToolUtils.kt @@ -6,7 +6,6 @@ import net.minecraft.core.HolderSet import net.minecraft.core.component.DataComponents import net.minecraft.tags.BlockTags import net.minecraft.tags.TagKey -import net.minecraft.world.item.SwordItem import net.minecraft.world.level.block.state.pattern.BlockInWorld import org.bukkit.GameMode import org.bukkit.Material @@ -20,7 +19,6 @@ import org.bukkit.potion.PotionEffectType import xyz.xenondevs.commons.collections.takeUnlessEmpty import xyz.xenondevs.nova.util.eyeInWater import xyz.xenondevs.nova.util.hardness -import xyz.xenondevs.nova.util.nmsItem import xyz.xenondevs.nova.util.nmsState import xyz.xenondevs.nova.util.novaBlock import xyz.xenondevs.nova.util.roundToDecimalPlaces @@ -91,7 +89,7 @@ object ToolUtils { // hardcoded in BambooSaplingBlock and BambooStalkBlock, ignores block break speed attribute // https://bugs.mojang.com/browse/MC-275705 - if ((block.type == Material.BAMBOO || block.type == Material.BAMBOO_SAPLING) && tool?.type?.nmsItem is SwordItem) + if ((block.type == Material.BAMBOO || block.type == Material.BAMBOO_SAPLING) && VanillaToolCategories.SWORD in ToolCategory.ofItem(tool)) return 1.0 var damage = calculateDamage( diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt index 60856b10c4a..6f3ceaeb75f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt @@ -1,7 +1,6 @@ package xyz.xenondevs.nova.util.reflection import net.minecraft.core.BlockPos -import net.minecraft.core.BlockPos.MutableBlockPos import net.minecraft.core.Holder import net.minecraft.core.HolderSet import net.minecraft.core.Registry @@ -10,7 +9,6 @@ import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.LevelHeightAccessor import net.minecraft.world.level.LevelReader -import net.minecraft.world.level.WorldGenLevel import net.minecraft.world.level.biome.Biome import net.minecraft.world.level.biome.Biome.ClimateSettings import net.minecraft.world.level.biome.Biome.TemperatureModifier @@ -24,9 +22,7 @@ import net.minecraft.world.level.chunk.LevelChunkSection import net.minecraft.world.level.chunk.UpgradeData import net.minecraft.world.level.levelgen.blending.BlendingData import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext -import net.minecraft.world.level.levelgen.feature.OreFeature import net.minecraft.world.level.levelgen.feature.ReplaceBlockFeature -import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration.TargetBlockState import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorRule import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor @@ -42,7 +38,6 @@ import java.security.ProtectionDomain import java.util.* import net.minecraft.world.entity.LivingEntity as MojangLivingEntity import net.minecraft.world.item.ItemStack as MojangStack -import java.util.function.Function as JavaFunction // TODO: Retire ReflectionRegistry, put the fields as top level constants in the files that use them instead @Suppress("MemberVisibilityCanBePrivate") @@ -64,8 +59,6 @@ internal object ReflectionRegistry { val LIVING_ENTITY_PLAY_BLOCK_FALL_SOUND_METHOD = getMethod(MojangLivingEntity::class, true, "playBlockFallSound") val RULE_PROCESSOR_PROCESS_BLOCK_METHOD = getMethod(RuleProcessor::class, false, "processBlock", LevelReader::class, BlockPos::class, BlockPos::class, StructureTemplate.StructureBlockInfo::class, StructureTemplate.StructureBlockInfo::class, StructurePlaceSettings::class) val PROCESSOR_RULE_TEST_METHOD = getMethod(ProcessorRule::class, false, "test", BlockState::class, BlockState::class, BlockPos::class, BlockPos::class, BlockPos::class, RandomSource::class) - val ORE_FEATURE_CAN_PLACE_ORE_METHOD = getMethod(OreFeature::class, true, "canPlaceOre", BlockState::class, JavaFunction::class, RandomSource::class, OreConfiguration::class, TargetBlockState::class, MutableBlockPos::class) - val ORE_FEATURE_DO_PLACE_METHOD = getMethod(OreFeature::class, true, "doPlace", WorldGenLevel::class, RandomSource::class, OreConfiguration::class, Double::class, Double::class, Double::class, Double::class, Double::class, Double::class, Int::class, Int::class, Int::class, Int::class, Int::class) val REPLACE_BLOCK_PLACE_METHOD = getMethod(ReplaceBlockFeature::class, false, "place", FeaturePlaceContext::class) val RULE_TEST_TEST_METHOD = getMethod(RuleTest::class, false, "test", BlockState::class, RandomSource::class) val BLOCK_GETTER_GET_BLOCK_STATE_METHOD = getMethod(BlockGetter::class, false, "getBlockState", BlockPos::class) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/world/BlockStateSearcher.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/world/BlockStateSearcher.kt index db5467ac6b5..55b93879c2c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/world/BlockStateSearcher.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/world/BlockStateSearcher.kt @@ -5,7 +5,6 @@ package xyz.xenondevs.nova.util.world import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import it.unimi.dsi.fastutil.ints.IntSet import net.minecraft.util.BitStorage import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap import net.minecraft.util.ZeroBitStorage @@ -76,7 +75,11 @@ object BlockStateSearcher { break val resultList = result.getOrSet(queryIdx, ::ArrayList) - storage.runOnIds(ids.keys) { id, encodedPos -> + for (encodedPos in 0.. Unit) { - val bits = bits - val data = raw - - val valuesPerLong = 64 / bits - val mask = (1L shl bits) - 1L - - var idx = 0 - - for (l in data) { - var l = l - repeat(valuesPerLong) { - val id = (l and mask).toInt() - - if (find.contains(id)) - run(id, idx) - - l = l shr bits - idx++ - } - } - } - } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/DefaultBlocks.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/DefaultBlocks.kt index 41bc2e3b9a3..6777d7bab12 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/DefaultBlocks.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/DefaultBlocks.kt @@ -63,7 +63,10 @@ internal object DefaultBlocks { behaviors( TripwireBehavior, BlockSounds(SoundGroup.STONE), - Breakable(hardness = 0.0) + Breakable( + hardness = 0.0, + requiresToolForDrops = false + ) ) stateProperties( DefaultScopedBlockStateProperties.TRIPWIRE_NORTH, diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt index e4607015af6..dbeb18cb84e 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt @@ -86,11 +86,13 @@ interface BlockBehavior : BlockBehaviorHolder { /** * Retrieves the amount of experience that would be dropped when breaking a block of [state] at [pos] with the given [ctx]. + * Handlers should check [DefaultContextParamTypes.BLOCK_EXP_DROPS]. */ fun getExp(pos: BlockPos, state: NovaBlockState, ctx: Context): Int = 0 /** * Retrieves the items that would be dropped when breaking a block of [state] at [pos] with the given [ctx]. + * Handlers should check [DefaultContextParamTypes.BLOCK_DROPS] and [DefaultContextParamTypes.BLOCK_STORAGE_DROPS]. */ fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List = emptyList() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockDrops.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockDrops.kt index cbb97cbc45a..ec03b53e0ea 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockDrops.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockDrops.kt @@ -1,6 +1,5 @@ package xyz.xenondevs.nova.world.block.behavior -import org.bukkit.GameMode import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.context.Context import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak @@ -17,7 +16,7 @@ import xyz.xenondevs.nova.world.block.state.NovaBlockState object BlockDrops : BlockBehavior { override fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List { - if (ctx[DefaultContextParamTypes.SOURCE_PLAYER]?.gameMode == GameMode.CREATIVE) + if (!ctx[DefaultContextParamTypes.BLOCK_DROPS]) return emptyList() return state.block.item diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/LeavesBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/LeavesBehavior.kt index 4ed35305937..e8dc149f3a5 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/LeavesBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/LeavesBehavior.kt @@ -5,7 +5,7 @@ import net.minecraft.world.level.storage.loot.LootParams import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.phys.Vec3 -import org.bukkit.GameMode +import org.bukkit.Material import org.bukkit.Tag import org.bukkit.event.block.LeavesDecayEvent import org.bukkit.inventory.ItemStack @@ -19,6 +19,7 @@ import xyz.xenondevs.nova.util.callEvent import xyz.xenondevs.nova.util.serverLevel import xyz.xenondevs.nova.util.unwrap import xyz.xenondevs.nova.world.BlockPos +import xyz.xenondevs.nova.world.block.DefaultBlocks import xyz.xenondevs.nova.world.block.state.NovaBlockState import xyz.xenondevs.nova.world.block.state.property.DefaultBlockStateProperties.LEAVES_DISTANCE import xyz.xenondevs.nova.world.block.state.property.DefaultBlockStateProperties.LEAVES_PERSISTENT @@ -58,7 +59,7 @@ internal object LeavesBehavior : BlockBehavior { } override fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List { - if (ctx[DefaultContextParamTypes.SOURCE_PLAYER]?.gameMode == GameMode.CREATIVE) + if (!ctx[DefaultContextParamTypes.BLOCK_DROPS]) return emptyList() val nmsState = pos.nmsBlockState @@ -104,4 +105,22 @@ internal object LeavesBehavior : BlockBehavior { return distance } + override fun pickBlockCreative(pos: BlockPos, state: NovaBlockState, ctx: Context): ItemStack? { + val type = when (state.block) { + DefaultBlocks.OAK_LEAVES -> Material.OAK_LEAVES + DefaultBlocks.SPRUCE_LEAVES -> Material.SPRUCE_LEAVES + DefaultBlocks.BIRCH_LEAVES -> Material.BIRCH_LEAVES + DefaultBlocks.JUNGLE_LEAVES -> Material.JUNGLE_LEAVES + DefaultBlocks.ACACIA_LEAVES -> Material.ACACIA_LEAVES + DefaultBlocks.DARK_OAK_LEAVES -> Material.DARK_OAK_LEAVES + DefaultBlocks.MANGROVE_LEAVES -> Material.MANGROVE_LEAVES + DefaultBlocks.CHERRY_LEAVES -> Material.CHERRY_LEAVES + DefaultBlocks.AZALEA_LEAVES -> Material.AZALEA_LEAVES + DefaultBlocks.FLOWERING_AZALEA_LEAVES -> Material.FLOWERING_AZALEA_LEAVES + else -> throw UnsupportedOperationException("Unknown leaves block type: ${state.block}") + } + + return ItemStack.of(type) + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/NoteBlockBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/NoteBlockBehavior.kt index 531c32d69c3..b970454f2a9 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/NoteBlockBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/NoteBlockBehavior.kt @@ -6,7 +6,6 @@ import net.minecraft.sounds.SoundEvent import net.minecraft.sounds.SoundSource import net.minecraft.world.level.block.entity.SkullBlockEntity import net.minecraft.world.level.block.state.properties.NoteBlockInstrument -import org.bukkit.GameMode import org.bukkit.Instrument import org.bukkit.Material import org.bukkit.Note @@ -127,7 +126,7 @@ internal object NoteBlockBehavior : BlockBehavior { } override fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List { - if (ctx[DefaultContextParamTypes.SOURCE_PLAYER]?.gameMode == GameMode.CREATIVE) + if (!ctx[DefaultContextParamTypes.BLOCK_DROPS]) return emptyList() return listOf(ItemStack(Material.NOTE_BLOCK)) @@ -170,4 +169,8 @@ internal object NoteBlockBehavior : BlockBehavior { NoteBlockInstrument.CUSTOM_HEAD -> Instrument.CUSTOM_HEAD } + override fun pickBlockCreative(pos: BlockPos, state: NovaBlockState, ctx: Context): ItemStack? { + return ItemStack.of(Material.NOTE_BLOCK) + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TileEntityDrops.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TileEntityDrops.kt index edc4b010558..861de5ee24d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TileEntityDrops.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TileEntityDrops.kt @@ -1,8 +1,5 @@ package xyz.xenondevs.nova.world.block.behavior -import org.bukkit.GameMode -import org.bukkit.entity.Entity -import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.context.Context import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak @@ -21,9 +18,11 @@ import xyz.xenondevs.nova.world.format.WorldDataManager object TileEntityDrops : BlockBehavior { override fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List { - val sourceEntity: Entity? = ctx[DefaultContextParamTypes.SOURCE_ENTITY] + if (!ctx[DefaultContextParamTypes.BLOCK_DROPS] && !ctx[DefaultContextParamTypes.BLOCK_STORAGE_DROPS]) + return emptyList() + return WorldDataManager.getTileEntity(pos) - ?.getDrops(sourceEntity !is Player || sourceEntity.gameMode != GameMode.CREATIVE) + ?.getDrops(ctx[DefaultContextParamTypes.BLOCK_DROPS]) ?: emptyList() } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TripwireBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TripwireBehavior.kt index 8fb505a5111..49c6c21f048 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TripwireBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/TripwireBehavior.kt @@ -5,7 +5,6 @@ import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.TripWireBlock import net.minecraft.world.level.block.TripWireHookBlock import net.minecraft.world.level.block.state.BlockState -import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.block.BlockFace import org.bukkit.craftbukkit.event.CraftEventFactory @@ -72,7 +71,7 @@ internal object TripwireBehavior : BlockBehavior { } override fun getDrops(pos: BlockPos, state: NovaBlockState, ctx: Context): List { - if (ctx[DefaultContextParamTypes.SOURCE_PLAYER]?.gameMode == GameMode.CREATIVE) + if (!ctx[DefaultContextParamTypes.BLOCK_DROPS]) return emptyList() return listOf(ItemStack.of(Material.STRING)) @@ -142,4 +141,8 @@ internal object TripwireBehavior : BlockBehavior { .setValue(TripWireBlock.ATTACHED, nova.getOrThrow(TRIPWIRE_ATTACHED)) .setValue(TripWireBlock.POWERED, nova.getOrThrow(POWERED)) + override fun pickBlockCreative(pos: BlockPos, state: NovaBlockState, ctx: Context): ItemStack? { + return ItemStack.of(Material.STRING) + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt index 5ac8dfc1079..d8505d6c932 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt @@ -99,20 +99,18 @@ internal class VanillaBlockBreaker( @Suppress("MemberVisibilityCanBePrivate") internal sealed class BlockBreaker(val player: Player, val pos: BlockPos, val startSequence: Int, val blockedUntil: Int) { - protected val breakMethod: BreakMethod by lazy { createBreakMethod() } + private val breakMethod: BreakMethod by lazy { createBreakMethod() } - val block = pos.block - protected val soundGroup: SoundGroup? = if (SoundEngine.overridesSound(block.blockSoundGroup.hitSound)) block.novaSoundGroup else null - protected val hardness: Double = block.hardness - protected val tool: ItemStack? = player.inventory.itemInMainHand.takeUnlessEmpty() - protected val itemToolCategories: Set = ToolCategory.ofItem(tool) - protected val drops: Boolean = player.gameMode == GameMode.CREATIVE || ToolUtils.isCorrectToolForDrops(block, tool) + protected val block = pos.block + private val soundGroup: SoundGroup? = if (SoundEngine.overridesSound(block.blockSoundGroup.hitSound)) block.novaSoundGroup else null + private val hardness: Double = block.hardness + private val tool: ItemStack? = player.inventory.itemInMainHand.takeUnlessEmpty() + private val itemToolCategories: Set = ToolCategory.ofItem(tool) - var destroyTicks = 0 - private set + private var destroyTicks = 0 var progress = 0.0 private set - val isDone: Boolean + private val isDone: Boolean get() = progress >= 1 var isStopped: Boolean = false @@ -180,29 +178,32 @@ internal sealed class BlockBreaker(val player: Player, val pos: BlockPos, val st fun breakBlock(brokenClientside: Boolean, sequence: Int) { // create a block breaking context - val ctx = Context.intention(BlockBreak) + val ctxBuilder = Context.intention(BlockBreak) .param(DefaultContextParamTypes.BLOCK_POS, pos) .param(DefaultContextParamTypes.SOURCE_ENTITY, player) .param(DefaultContextParamTypes.TOOL_ITEM_STACK, tool) - .param(DefaultContextParamTypes.BLOCK_DROPS, drops) + val ctx = ctxBuilder.build() val level = block.world.serverLevel val blockPos = pos.nmsPos // val event = BlockBreakEvent(block, player) - if (drops) { - event.expToDrop = when (this) { - is NovaBlockBreaker -> blockType.getExp(pos, blockState, ctx.build()) - is VanillaBlockBreaker -> BlockUtils.getVanillaBlockExp(level, blockPos, tool.unwrap().copy()) + event.expToDrop = when (this) { + is NovaBlockBreaker -> blockType.getExp(pos, blockState, ctxBuilder.build()) + is VanillaBlockBreaker -> { + if (ctx[DefaultContextParamTypes.BLOCK_EXP_DROPS]) + BlockUtils.getVanillaBlockExp(level, blockPos, tool.unwrap().copy()) + else 0 } } + callEvent(event) - ctx.param(DefaultContextParamTypes.BLOCK_DROPS, drops && event.isDropItems) + ctxBuilder.param(DefaultContextParamTypes.BLOCK_DROPS, ctx[DefaultContextParamTypes.BLOCK_DROPS] && event.isDropItems) + ctxBuilder.param(DefaultContextParamTypes.BLOCK_STORAGE_DROPS, ctx[DefaultContextParamTypes.BLOCK_STORAGE_DROPS] && event.isDropItems) // if (!event.isCancelled && !ProtectionManager.isVanillaProtected(player, block.location)) { - // // if (level.gameRules.getBoolean(GameRules.RULE_DOBLOCKDROPS)) { val exp = event.expToDrop @@ -233,7 +234,7 @@ internal sealed class BlockBreaker(val player: Player, val pos: BlockPos, val st val state = block.state // remove block - val items = BlockUtils.breakBlockInternal(ctx.build(), !brokenClientside) + val items = BlockUtils.breakBlockInternal(ctxBuilder.build(), !brokenClientside) val itemEntities = EntityUtils.createBlockDropItemEntities(pos, items) // drop items diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/migrator/BlockMigrator.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/migrator/BlockMigrator.kt index a4cf6ef9449..34fe0b02c01 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/migrator/BlockMigrator.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/migrator/BlockMigrator.kt @@ -29,6 +29,7 @@ import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.integration.customitems.CustomItemServiceManager import xyz.xenondevs.nova.patch.impl.worldgen.chunksection.LevelChunkSectionWrapper import xyz.xenondevs.nova.resources.ResourceGeneration +import xyz.xenondevs.nova.resources.lookup.ResourceLookups import xyz.xenondevs.nova.util.bukkitMaterial import xyz.xenondevs.nova.util.levelChunk import xyz.xenondevs.nova.util.registerEvents @@ -188,6 +189,10 @@ internal object BlockMigrator : Listener { @JvmStatic fun migrateBlockState(pos: BlockPos, blockState: BlockState): BlockState { + // disable migrations for all block states used by base packs + if (blockState in ResourceLookups.OCCUPIED_BLOCK_STATES) + return blockState + try { val migration = migrationsByVanillaBlock[blockState.block] ?: return blockState @@ -249,10 +254,12 @@ internal object BlockMigrator : Listener { } // Migrations for block types that are also used as backing states - val migration = migrationsByVanillaBlock[newState.block] - if (migration is ComplexBlockMigration) { - val novaState = migration.vanillaToNova(newState) - WorldDataManager.setBlockState(pos, novaState) + if (newState !in ResourceLookups.OCCUPIED_BLOCK_STATES) { + val migration = migrationsByVanillaBlock[newState.block] + if (migration is ComplexBlockMigration) { + val novaState = migration.vanillaToNova(newState) + WorldDataManager.setBlockState(pos, novaState) + } } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/BackingStateConfig.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/BackingStateConfig.kt index cf8ae61c8b9..c9b38484d73 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/BackingStateConfig.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/BackingStateConfig.kt @@ -34,6 +34,7 @@ internal abstract class BackingStateConfigType internal abstract val blockedIds: Set abstract val properties: Set + abstract val isWaterloggable: Boolean abstract fun of(id: Int, waterlogged: Boolean = false): T abstract fun of(properties: Map): T diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/LeavesBackingStateConfigType.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/LeavesBackingStateConfigType.kt index 0b8df639066..ddf27d8124a 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/LeavesBackingStateConfigType.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/LeavesBackingStateConfigType.kt @@ -24,6 +24,7 @@ internal abstract class LeavesBackingStateConfigType(1149, "note_block") { override val properties = hashSetOf("instrument", "note", "powered") + override val isWaterloggable = false fun getIdOf(instrument: NoteBlockInstrument, note: Int, powered: Boolean): Int { return instrument.ordinal * NOTE_BASE * POWERED_BASE + note * POWERED_BASE + powered.intValue diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/SidedBackingStateConfig.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/SidedBackingStateConfig.kt index 3a1e8350459..4a0405d32fc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/SidedBackingStateConfig.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/SidedBackingStateConfig.kt @@ -39,6 +39,7 @@ internal abstract class SidedBackingStateConfigType override val blockedIds = setOf(63) override val defaultStateConfig = of(63) override val properties = hashSetOf("north", "east", "south", "west", "up", "down") + override val isWaterloggable = false final override fun of(id: Int, waterlogged: Boolean): T { if (waterlogged) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/TripwireBackingStateConfig.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/TripwireBackingStateConfig.kt index cf3d1b110ff..c50c8c1c7c3 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/TripwireBackingStateConfig.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/state/model/TripwireBackingStateConfig.kt @@ -53,6 +53,7 @@ internal abstract class TripwireBackingStateConfigType private constructor( override val blockedIds = IntArraySet((0..15).toIntArray()) override val properties = hashSetOf("north", "east", "south", "west", "disarmed", "powered") + override val isWaterloggable = false override fun of(id: Int, waterlogged: Boolean): TripwireBackingStateConfig { if (waterlogged) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/TileEntity.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/TileEntity.kt index 448edba96b5..c720966b13b 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/TileEntity.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/TileEntity.kt @@ -309,9 +309,9 @@ abstract class TileEntity( }.get() if (preUpdateHandler != null) - inventory.setPreUpdateHandler(preUpdateHandler) + inventory.addPreUpdateHandler(preUpdateHandler) if (postUpdateHandler != null) - inventory.setPostUpdateHandler(postUpdateHandler) + inventory.addPostUpdateHandler(postUpdateHandler) if (!persistent) dropProvider { inventory.items.filterNotNull() } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/menu/MenuContainer.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/menu/MenuContainer.kt index 229acd8afbe..fa629fb97cb 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/menu/MenuContainer.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/menu/MenuContainer.kt @@ -6,6 +6,7 @@ import xyz.xenondevs.nova.world.block.tileentity.TileEntity import xyz.xenondevs.nova.world.block.tileentity.TileEntity.GlobalTileEntityMenu import xyz.xenondevs.nova.world.block.tileentity.TileEntity.IndividualTileEntityMenu import java.util.* +import kotlin.math.max import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter @@ -16,13 +17,20 @@ import kotlin.reflect.jvm.isAccessible abstract class MenuContainer internal constructor() { - private val openWindows = ArrayList() + private val openWindows = HashSet() + protected val pendingWindows = WeakHashMap() fun registerWindow(window: Window) { - window.addOpenHandler { openWindows += window } + pendingWindows.compute(window.viewer) { _, value -> (value ?: 0) + 1 } + + window.addOpenHandler { + openWindows += window + pendingWindows.compute(window.viewer) { _, value -> max((value ?: 0) - 1, 0) } + handleOpened(window.viewer) + } window.addCloseHandler { openWindows -= window - handleClosed(window.viewer!!) + handleClosed(window.viewer) } } @@ -40,6 +48,8 @@ abstract class MenuContainer internal constructor() { abstract fun openWindow(player: Player) + protected abstract fun handleOpened(player: Player) + protected abstract fun handleClosed(player: Player) @PublishedApi @@ -83,13 +93,16 @@ internal class IndividualMenuContainer internal constructor( } override fun handleClosed(player: Player) { - menus.remove(player) + if ((pendingWindows[player] ?: 0) == 0) + menus.remove(player) } override fun getMenusInternal(): Sequence { return menus.values.asSequence() } + override fun handleOpened(player: Player) = Unit + } internal class GlobalMenuContainer internal constructor( @@ -110,12 +123,15 @@ internal class GlobalMenuContainer internal constructor( override fun openWindow(player: Player) { menu = ctor.call(tileEntity) .also { it.openWindow(player) } + } + + override fun handleOpened(player: Player) { viewers++ } override fun handleClosed(player: Player) { viewers-- - if (viewers <= 0) { + if (viewers <= 0 && pendingWindows.all { (_, amountPending) -> amountPending == 0 }) { menu = null } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkConfigurator.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkConfigurator.kt index 6117c210005..cb4a6463581 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkConfigurator.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkConfigurator.kt @@ -205,6 +205,14 @@ internal class NetworkConfigurator(private val world: World, private val ticker: private suspend fun buildClusters(): List = coroutineScope { state.mutex.withLock { + // init clusters TODO: clusters are only uninitialized here if an exception was thrown in the task, but it may make sense to always init here + for (network in state.networks) { + if (network.cluster == null) { + LOGGER.error("Cluster of $network is uninitialized") + network.initCluster() + } + } + // collect proto clusters val protoClusters = state.networks .mapTo(HashSet()) { it.cluster ?: throw IllegalStateException("Cluster for $it is uninitialized") } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkManager.kt index 55d58f39a27..85fd4661468 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkManager.kt @@ -215,6 +215,14 @@ object NetworkManager : Listener { return null } + /** + * Checks whether it is [unknown][NetworkNodeProvider.isUnknown] if the block at [pos] is a [NetworkNode] + * using the registered [NetworkNodeProviders][NetworkNodeProvider]. + */ + suspend fun isUnknown(pos: BlockPos): Boolean { + return nodeProviders.any { it.isUnknown(pos) } + } + @EventHandler private fun handleWorldUnload(event: WorldUnloadEvent) { removeConfigurator(event.world) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkNodeProvider.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkNodeProvider.kt index 6987cb82140..8f1be848c77 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkNodeProvider.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/NetworkNodeProvider.kt @@ -2,6 +2,7 @@ package xyz.xenondevs.nova.world.block.tileentity.network import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.ChunkPos +import xyz.xenondevs.nova.world.block.DefaultBlocks import xyz.xenondevs.nova.world.block.tileentity.TileEntity import xyz.xenondevs.nova.world.block.tileentity.network.node.NetworkNode import xyz.xenondevs.nova.world.block.tileentity.vanilla.VanillaTileEntity @@ -22,6 +23,14 @@ interface NetworkNodeProvider { */ suspend fun getNodes(pos: ChunkPos): Sequence + /** + * Checks whether the block at the specified [pos] is unknown. + * For example, blocks of addons that weren't loaded but may be a [NetworkNode] should be considered unknown. + * + * This information may be used by the network system to determine whether to delete network data or not. + */ + suspend fun isUnknown(pos: BlockPos): Boolean = false + } /** @@ -33,6 +42,10 @@ internal object NovaNetworkNodeProvider : NetworkNodeProvider { return WorldDataManager.getOrLoadTileEntity(pos) as? NetworkNode } + override suspend fun isUnknown(pos: BlockPos): Boolean { + return WorldDataManager.getOrLoadBlockState(pos)?.block == DefaultBlocks.UNKNOWN + } + override suspend fun getNodes(pos: ChunkPos): Sequence { return WorldDataManager.getOrLoadTileEntities(pos) .asSequence() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddBridgeTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddBridgeTask.kt index 2219327cfbe..3b9c7d41b73 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddBridgeTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddBridgeTask.kt @@ -18,9 +18,6 @@ import xyz.xenondevs.nova.world.block.tileentity.network.type.NetworkType import xyz.xenondevs.nova.world.format.NetworkState import xyz.xenondevs.nova.world.format.chunk.NetworkBridgeData import java.util.* -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set internal class AddBridgeTask( state: NetworkState, @@ -128,7 +125,7 @@ internal class AddBridgeTask( // depending on how many networks there are, perform the required action val network = when { // Merge network - previousNetworks.size > 1 -> mergeNetworks(self, networkType, previousNetworks) + previousNetworks.size > 1 -> mergeNetworks(networkType, previousNetworks) // Connect to existing network previousNetworks.size == 1 -> previousNetworks.first() // Make a new network @@ -146,7 +143,6 @@ internal class AddBridgeTask( * Merges the given [networks] into a single network of [type]. */ private suspend fun > mergeNetworks( - self: NetworkBridge, type: NetworkType, networks: Set> ): ProtoNetwork { @@ -197,4 +193,8 @@ internal class AddBridgeTask( } } + override fun toString(): String { + return "AddBridgeTask(bridge=$node, supportedNetworkTypes=$supportedNetworkTypes, bridgeFaces=$bridgeFaces)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddEndPointTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddEndPointTask.kt index 4df5cb30c6f..0dcddcf5f0f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddEndPointTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/AddEndPointTask.kt @@ -97,4 +97,8 @@ internal class AddEndPointTask( return false } + override fun toString(): String { + return "AddEndPointTask(endPoint=$node)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/LoadChunkTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/LoadChunkTask.kt index 8c581fb60eb..7638925097a 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/LoadChunkTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/LoadChunkTask.kt @@ -8,6 +8,7 @@ import xyz.xenondevs.commons.guava.component1 import xyz.xenondevs.commons.guava.component2 import xyz.xenondevs.commons.guava.component3 import xyz.xenondevs.commons.guava.iterator +import xyz.xenondevs.nova.LOGGER import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.block.tileentity.network.NetworkManager import xyz.xenondevs.nova.world.block.tileentity.network.ProtoNetwork @@ -42,14 +43,22 @@ internal class LoadChunkTask( val updatedNetworks = HashMap, MutableSet>() val chunkNodes = NetworkManager.getNodes(chunkPos).associateByTo(HashMap(), NetworkNode::pos) - val networkNodes = state.storage.getOrLoadNetworkRegion(chunkPos).getChunk(chunkPos).getData() + val networkChunk = state.storage.getOrLoadNetworkRegion(chunkPos).getChunk(chunkPos) + val networkNodes = networkChunk.getData() for ((pos, data) in networkNodes) { val node = chunkNodes[pos] - if (node == null || node in state) + + // the network data of unknown nodes should not be removed in order to prevent data loss of addons that weren't loaded + if (node == null && NetworkManager.isUnknown(pos)) continue when { + node != null && node in state -> { + LOGGER.error("Node at pos $pos is already loaded", Exception()) + continue + } + node is NetworkBridge && data is NetworkBridgeData -> { val networks = data.networks for ((type, id) in networks) { @@ -68,7 +77,12 @@ internal class LoadChunkTask( } } - else -> throw IllegalStateException("Node type and data type do not match") + else -> { + // node is null or node and data type do not match + LOGGER.error("Node type and data type mismatch: $node does not match $data. (Removing from network data storage)", Exception()) + networkChunk.setData(pos, null) + continue + } } state += node @@ -85,4 +99,8 @@ internal class LoadChunkTask( return updatedNetworks.isNotEmpty() } + override fun toString(): String { + return "LoadChunkTask(pos=$chunkPos)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveBridgeTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveBridgeTask.kt index 638effdd21d..7b1fa9150c2 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveBridgeTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveBridgeTask.kt @@ -17,9 +17,6 @@ import xyz.xenondevs.nova.world.format.NetworkState import xyz.xenondevs.nova.world.format.chunk.NetworkBridgeData import xyz.xenondevs.nova.world.format.chunk.NetworkEndPointData import java.util.* -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.filterTo internal class RemoveBridgeTask( state: NetworkState, @@ -215,4 +212,8 @@ internal class RemoveBridgeTask( return potentialNetworks } + override fun toString(): String { + return "RemoveBridgeTask(bridge=$node)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveEndPointTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveEndPointTask.kt index 41dcec8cc75..59bd0158644 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveEndPointTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/RemoveEndPointTask.kt @@ -57,4 +57,8 @@ internal class RemoveEndPointTask( } } + override fun toString(): String { + return "RemoveEndPointTask(node=$node)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/UnloadChunkTask.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/UnloadChunkTask.kt index e3f0784f8f4..24470126595 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/UnloadChunkTask.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/task/UnloadChunkTask.kt @@ -79,4 +79,8 @@ internal class UnloadChunkTask( return true } + override fun toString(): String { + return "UnloadChunkTask(pos=$chunkPos)" + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/energy/holder/DefaultEnergyHolder.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/energy/holder/DefaultEnergyHolder.kt index a0568b5cdac..d5ac5df4ed2 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/energy/holder/DefaultEnergyHolder.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/energy/holder/DefaultEnergyHolder.kt @@ -10,9 +10,8 @@ import xyz.xenondevs.commons.provider.Provider import xyz.xenondevs.commons.provider.observed import xyz.xenondevs.commons.provider.orElseNew import xyz.xenondevs.nova.serialization.DataHolder -import xyz.xenondevs.nova.util.TimedResettingLong +import xyz.xenondevs.nova.util.TickResettingLong import xyz.xenondevs.nova.world.block.tileentity.network.type.NetworkConnectionType -import xyz.xenondevs.nova.world.block.tileentity.network.type.energy.EnergyNetwork import kotlin.math.max import kotlin.math.min @@ -35,8 +34,8 @@ class DefaultEnergyHolder( ) : EnergyHolder { private val _energyProvider: MutableProvider = energy - private val _energyMinus = TimedResettingLong(EnergyNetwork.TICK_DELAY_PROVIDER) - private val _energyPlus = TimedResettingLong(EnergyNetwork.TICK_DELAY_PROVIDER) + private val _energyMinus = TickResettingLong() + private val _energyPlus = TickResettingLong() override val blockedFaces = blockedFaces.toEnumSet() override val connectionConfig: MutableMap @@ -59,15 +58,13 @@ class DefaultEnergyHolder( */ val energyProvider: Provider get() = _energyProvider - private var energyDeltaLastResetTick = 0 - /** - * The amount of energy that was extracted during the last energy network tick. + * The amount of energy that was extracted during the last server tick. */ val energyMinus: Long by _energyMinus /** - * The amount of energy that was inserted during the last energy network tick. + * The amount of energy that was inserted during the last server tick. */ val energyPlus: Long by _energyPlus diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedMultiVirtualInventory.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedMultiVirtualInventory.kt index ad3958fd9cd..a3e3d169cb2 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedMultiVirtualInventory.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedMultiVirtualInventory.kt @@ -60,7 +60,7 @@ internal class NetworkedMultiVirtualInventory( override fun canTake(slot: Int, amount: Int): Boolean { val inv = inventoryBySlot[slot] - if (inv.preUpdateHandler == null) + if (inv.preUpdateHandlers.isEmpty()) return true val invSlot = invSlotBySlot[slot] diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedVirtualInventory.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedVirtualInventory.kt index 025a0313ae9..8ff3ed15410 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedVirtualInventory.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/tileentity/network/type/item/inventory/NetworkedVirtualInventory.kt @@ -22,7 +22,7 @@ open class NetworkedInvUIInventory( } override fun canTake(slot: Int, amount: Int): Boolean { - if (inventory.preUpdateHandler == null) + if (inventory.preUpdateHandlers.isEmpty()) return true val itemStack = inventory.getUnsafeItem(slot) ?: return true diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/WorldDataManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/WorldDataManager.kt index 35809d3e5e4..e8d0413b2b1 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/WorldDataManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/WorldDataManager.kt @@ -99,6 +99,9 @@ object WorldDataManager : Listener { fun getBlockState(pos: BlockPos): NovaBlockState? = getChunkOrThrow(pos.chunkPos).getBlockState(pos) + internal suspend fun getOrLoadBlockState(pos: BlockPos): NovaBlockState? = + getOrLoadChunk(pos.chunkPos).getBlockState(pos) + fun setBlockState(pos: BlockPos, state: NovaBlockState?): NovaBlockState? { check(!disabled) { "WorldDataManager is already disabled" } return getChunkOrThrow(pos.chunkPos).setBlockState(pos, state) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/legacy/v1/LegacyRegionFileReaderV1.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/legacy/v1/LegacyRegionFileReaderV1.kt index 097d08c8848..3fbc1ffd20a 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/legacy/v1/LegacyRegionFileReaderV1.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/format/legacy/v1/LegacyRegionFileReaderV1.kt @@ -5,9 +5,6 @@ import org.bukkit.block.BlockFace import xyz.xenondevs.cbf.CBF import xyz.xenondevs.cbf.Compound import xyz.xenondevs.cbf.io.ByteReader -import xyz.xenondevs.nova.context.Context -import xyz.xenondevs.nova.context.intention.DefaultContextIntentions -import xyz.xenondevs.nova.context.param.DefaultContextParamTypes import xyz.xenondevs.nova.registry.NovaRegistries import xyz.xenondevs.nova.util.getValue import xyz.xenondevs.nova.world.BlockPos @@ -81,14 +78,9 @@ internal object LegacyRegionFileReaderV1 : LegacyRegionizedFileReader(reader)!! val blockFacing = compound.get("facing") // facing was the only built-in block property - // place context is used to determine correct block state - val placeCtx = Context.intention(DefaultContextIntentions.BlockPlace) - .param(DefaultContextParamTypes.BLOCK_POS, pos) - .param(DefaultContextParamTypes.BLOCK_TYPE_NOVA, type) - .param(DefaultContextParamTypes.SOURCE_DIRECTION, (blockFacing ?: BlockFace.NORTH).oppositeFace.direction) - .build() - - val blockState = type.chooseBlockState(placeCtx) + val blockState = blockFacing + ?.let { runCatching { type.defaultBlockState.with(DefaultBlockStateProperties.FACING, blockFacing) }.getOrNull() } + ?: type.defaultBlockState chunk.setBlockState(pos, blockState) if (type is NovaTileEntityBlock) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/DefaultGuiItems.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/DefaultGuiItems.kt index ecbc9401500..5f9b53397a1 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/DefaultGuiItems.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/DefaultGuiItems.kt @@ -4,6 +4,7 @@ import net.kyori.adventure.key.Key import xyz.xenondevs.nova.initialize.InternalInit import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.resources.builder.ResourcePackBuilder +import xyz.xenondevs.nova.resources.builder.layout.item.ItemModelCreationScope import xyz.xenondevs.nova.resources.builder.layout.item.ItemModelDefinitionBuilder import xyz.xenondevs.nova.resources.builder.layout.item.ItemModelSelectorScope import xyz.xenondevs.nova.util.data.writeImage @@ -87,6 +88,7 @@ object DefaultGuiItems { val BAR_ORANGE = barGuiItem("gui/opaque/bar/orange", Color(232, 76, 0), true) val CHEATING_ON = guiItem("cheating_on", "menu.nova.items.cheat_mode.on") val CHEATING_OFF = guiItem("cheating_off", "menu.nova.items.cheat_mode.off") + @Deprecated("Color picker will be removed in a future version") val COLOR_PICKER = guiItem("color_picker", "menu.nova.color_picker") val NUMBER = hiddenItem("gui/opaque/number") { model = numberedModels(0..999) { @@ -97,6 +99,17 @@ object DefaultGuiItems { val STOPWATCH = guiItem("stopwatch") // + /** + * An 18x18 scale 1 [canvas][ItemModelCreationScope.canvasModel]. + */ + val CANVAS = item("gui/canvas") { + hidden(true) + name(null) + modelDefinition { + model = canvasModel(18, 18) + } + } + // // legacy InvUI gui items val TP_LINE_CORNER_BOTTOM_LEFT = tpGuiItem("line/corner_bottom_left", null, true) @@ -168,6 +181,7 @@ object DefaultGuiItems { val TP_BAR_ORANGE = barGuiItem("gui/transparent/bar/orange", Color(232, 76, 0), false) val TP_CHEATING_ON = tpGuiItem("cheating_on", "menu.nova.items.cheat_mode.on") val TP_CHEATING_OFF = tpGuiItem("cheating_off", "menu.nova.items.cheat_mode.off") + @Deprecated("Color picker will be removed in a future version") val TP_COLOR_PICKER = tpGuiItem("color_picker", "menu.nova.color_picker") val TP_NUMBER = hiddenItem("gui/transparent/number") { model = numberedModels(0..999) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/ItemCategories.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/ItemCategories.kt index f1451f909d3..3797e9b7513 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/ItemCategories.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/ItemCategories.kt @@ -4,8 +4,11 @@ import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.bukkit.event.inventory.ClickType import org.bukkit.inventory.ItemStack -import org.spongepowered.configurate.CommentedConfigurationNode +import org.bukkit.persistence.PersistentDataType import xyz.xenondevs.commons.collections.takeUnlessEmpty +import xyz.xenondevs.commons.provider.Provider +import xyz.xenondevs.commons.provider.flatMapCollection +import xyz.xenondevs.commons.provider.map import xyz.xenondevs.invui.item.AbstractItem import xyz.xenondevs.invui.item.Click import xyz.xenondevs.invui.item.ItemProvider @@ -14,36 +17,19 @@ import xyz.xenondevs.nova.addon.AddonBootstrapper import xyz.xenondevs.nova.addon.id import xyz.xenondevs.nova.addon.name import xyz.xenondevs.nova.config.Configs -import xyz.xenondevs.nova.initialize.InitFun -import xyz.xenondevs.nova.initialize.InternalInit -import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.registry.NovaRegistries -import xyz.xenondevs.nova.ui.menu.explorer.creative.ItemsWindow +import xyz.xenondevs.nova.ui.menu.explorer.creative.ItemsMenu import xyz.xenondevs.nova.ui.menu.explorer.recipes.handleRecipeChoiceItemClick import xyz.xenondevs.nova.util.addItemCorrectly import xyz.xenondevs.nova.util.data.get import xyz.xenondevs.nova.util.item.ItemUtils import xyz.xenondevs.nova.util.item.novaItem -@InternalInit(stage = InternalInitStage.POST_WORLD) internal object ItemCategories { - lateinit var CATEGORIES: List - private set - lateinit var OBTAINABLE_ITEMS: List - private set - - @InitFun - private fun init() { - val cfg = Configs["nova:item_categories"] - reload(cfg.get()) - cfg.subscribe(::reload) - } - - private fun reload(cfg: CommentedConfigurationNode) { - CATEGORIES = cfg.get>()?.takeUnlessEmpty() ?: getDefaultItemCategories() - OBTAINABLE_ITEMS = CATEGORIES.flatMap(ItemCategory::items) - } + val categories: Provider> = Configs["nova:item_categories"] + .map { it.get>()?.takeUnlessEmpty() ?: getDefaultItemCategories() } + val obtainableItems: Provider> = categories.flatMapCollection(ItemCategory::items) private fun getDefaultItemCategories(): List = AddonBootstrapper.addons @@ -73,7 +59,9 @@ internal class CategorizedItem(val id: String) : AbstractItem() { override fun getItemProvider(player: Player) = itemProvider override fun handleClick(clickType: ClickType, player: Player, click: Click) { - if (player in ItemsWindow.cheaters && player.hasPermission("nova.command.give")) { + if (player.hasPermission("nova.command.give") + && player.persistentDataContainer.get(ItemsMenu.CHEAT_MODE_KEY, PersistentDataType.BOOLEAN) == true + ) { if (clickType == ClickType.MIDDLE) { player.setItemOnCursor(itemStack.clone().apply { amount = novaItem?.maxStackSize ?: type.maxStackSize }) } else if (clickType.isShiftClick) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/NovaItem.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/NovaItem.kt index ea2263c8dc6..9507506e644 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/NovaItem.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/NovaItem.kt @@ -150,7 +150,7 @@ class NovaItem internal constructor( * Creates an [ItemStack] of this [NovaItem] with the given [amount] in server-side format. */ fun createItemStack(amount: Int = 1): ItemStack = - MojangStack(PacketItems.SERVER_SIDE_ITEM_HOLDER, 1, defaultPatch).asBukkitMirror() + MojangStack(PacketItems.SERVER_SIDE_ITEM_HOLDER, amount, defaultPatch).asBukkitMirror() /** * Creates an [ItemBuilder] for an [ItemStack] of this [NovaItem], in client-side format, diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/behavior/Stripping.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/behavior/Stripping.kt index 7a29ea633ff..94f4c6065d0 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/behavior/Stripping.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/behavior/Stripping.kt @@ -46,7 +46,8 @@ private val STRIPPABLES: Map = mapOf( Blocks.CRIMSON_HYPHAE to Blocks.STRIPPED_CRIMSON_HYPHAE, Blocks.MANGROVE_WOOD to Blocks.STRIPPED_MANGROVE_WOOD, Blocks.MANGROVE_LOG to Blocks.STRIPPED_MANGROVE_LOG, - Blocks.CHERRY_LOG to Blocks.STRIPPED_CHERRY_LOG + Blocks.CHERRY_LOG to Blocks.STRIPPED_CHERRY_LOG, + Blocks.PALE_OAK_LOG to Blocks.STRIPPED_PALE_OAK_LOG ) /** diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConversion.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConversion.kt index 23af868a6c9..8706e85e559 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConversion.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConversion.kt @@ -51,7 +51,6 @@ internal object ItemStackLegacyConversion { NamespacedKey("nova", "damage"), )) - registerConverter(ItemStackSubIdToModelIdConverter) registerConverter(ItemStackNovaDamageConverter) registerConverter(ItemStackEnchantmentsConverter) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConverter.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConverter.kt index b363750e756..173fb0b5734 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConverter.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/legacy/ItemStackLegacyConverter.kt @@ -13,7 +13,6 @@ import org.bukkit.NamespacedKey import xyz.xenondevs.cbf.CBF import xyz.xenondevs.nova.serialization.cbf.NamespacedCompound import xyz.xenondevs.nova.util.data.getByteArrayOrNull -import xyz.xenondevs.nova.util.data.getIntOrNull import xyz.xenondevs.nova.util.data.getOrNull import xyz.xenondevs.nova.util.getOrThrow import kotlin.jvm.optionals.getOrNull @@ -80,20 +79,6 @@ internal class ItemStackPersistentDataConverter( } -internal data object ItemStackSubIdToModelIdConverter : ItemStackTagLegacyConverter() { - - override fun convert(tag: CompoundTag) { - val novaTag = tag.getOrNull("nova") - ?: return - val subId = novaTag.getIntOrNull("subId") - if (subId == null || subId == 0) { - novaTag.remove("subId") - novaTag.putString("modelId", "default") - } - } - -} - internal data object ItemStackNovaDamageConverter : ItemStackLegacyConverter { @Suppress("DEPRECATION") diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/logic/PacketItems.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/logic/PacketItems.kt index ce12fc460aa..ac9e7cec103 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/logic/PacketItems.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/item/logic/PacketItems.kt @@ -86,8 +86,7 @@ import xyz.xenondevs.nova.util.unwrap import xyz.xenondevs.nova.world.item.NovaItem import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType -import java.util.Optional -import kotlin.collections.map +import java.util.* import com.mojang.datafixers.util.Pair as MojangPair import net.minecraft.world.item.ItemStack as MojangStack @@ -404,13 +403,11 @@ internal object PacketItems : Listener, PacketListener { return type == DataComponents.CUSTOM_DATA } - private fun getUnknownItem(itemStack: MojangStack, id: String?, modelId: String = "default"): MojangStack { + private fun getUnknownItem(itemStack: MojangStack, id: String?): MojangStack { return MojangStack(Items.BARRIER).apply { set( DataComponents.ITEM_NAME, - Component.literal( - "Unknown item: $id" + if (modelId != "default") ":$modelId" else "" - ).withStyle(ChatFormatting.RED) + Component.literal("Unknown item: $id").withStyle(ChatFormatting.RED) ) storeServerSideTag(this, itemStack) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/loot/LootGeneration.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/loot/LootGeneration.kt index f6f7e10a3be..2fd24c16532 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/loot/LootGeneration.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/loot/LootGeneration.kt @@ -30,7 +30,8 @@ internal object LootGeneration : Listener { @EventHandler private fun handleLootGenerationEvent(event: LootGenerateEvent) { lootTables.forEach { loot -> - if (loot.isAllowed(event.lootTable.key)) + @Suppress("SENSELESS_COMPARISON") // improperly annotated + if (event.lootTable != null && loot.isAllowed(event.lootTable.key)) event.loot.addAll(loot.getRandomItems()) } } diff --git a/nova/src/main/resources/assets/nova/fonts/waila.json b/nova/src/main/resources/assets/nova/fonts/waila.json index 7f8c01feb02..4425715c612 100644 --- a/nova/src/main/resources/assets/nova/fonts/waila.json +++ b/nova/src/main/resources/assets/nova/fonts/waila.json @@ -184,7 +184,7 @@ }, { "file": "nova:font/waila/10/end.png", - "chars": ["\uf019"], + "chars": ["\uf01a"], "height": 128, "ascent": 0, "type": "bitmap" diff --git a/nova/src/main/resources/assets/nova/lang/bg_bg.json b/nova/src/main/resources/assets/nova/lang/bg_bg.json index 81fc5b12e87..007dd38dcf4 100644 --- a/nova/src/main/resources/assets/nova/lang/bg_bg.json +++ b/nova/src/main/resources/assets/nova/lang/bg_bg.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Не успя да презареди рецепти, проверете конзолата за повече информация.", "command.nova.reload_configs.none": "Не са променени никакви конфигурации.", "command.nova.show_item_behaviors.success": "Този %s има %s поведение(я) на елемента:\n%s", - "command.nova.show_item_behaviors.no_item": "Моля, дръжте предмет в основната си ръка и опитайте отново." + "command.nova.show_item_behaviors.no_item": "Моля, дръжте предмет в основната си ръка и опитайте отново.", + "command.nova.recalculate_leave_properties.done": "Обработени са %s листа и %s листа не са постоянни.", + "command.nova.show_vanilla_block_state.success": "Гледате състоянието на блока %s.", + "command.nova.show_vanilla_block_state.failure": "Моля, погледнете блок и опитайте отново." } diff --git a/nova/src/main/resources/assets/nova/lang/cs_cz.json b/nova/src/main/resources/assets/nova/lang/cs_cz.json index 8ae5ad6f6e5..8bd50552782 100644 --- a/nova/src/main/resources/assets/nova/lang/cs_cz.json +++ b/nova/src/main/resources/assets/nova/lang/cs_cz.json @@ -161,5 +161,8 @@ "command.nova.reload_configs.failure": "Nepodařilo se načíst konfigurace, další informace získáte v konzoli.", "command.nova.reload_recipes.failure": "Nepodařilo se načíst recepty, další informace získáte v konzoli.", "command.nova.show_item_behaviors.success": "Tento %s má chování %s položek:\n%s", - "command.nova.show_item_behaviors.no_item": "Podržte prosím předmět v hlavní ruce a zkuste to znovu." + "command.nova.show_item_behaviors.no_item": "Podržte prosím předmět v hlavní ruce a zkuste to znovu.", + "command.nova.recalculate_leave_properties.done": "Zpracováno %s listů a %s listů se stalo nepersistentními.", + "command.nova.show_vanilla_block_state.success": "Díváte se na stav bloku %s.", + "command.nova.show_vanilla_block_state.failure": "Podívejte se prosím na blok a zkuste to znovu." } diff --git a/nova/src/main/resources/assets/nova/lang/da_dk.json b/nova/src/main/resources/assets/nova/lang/da_dk.json index 49c3310a995..5d41eb6a82e 100644 --- a/nova/src/main/resources/assets/nova/lang/da_dk.json +++ b/nova/src/main/resources/assets/nova/lang/da_dk.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Kunne ikke genindlæse opskrifter, tjek konsollen for mere information.", "command.nova.search_block.result": "Fandt %s på %s.", "command.nova.show_item_behaviors.success": "Denne %s har %s vareadfærd(er):\n%s", - "command.nova.show_item_behaviors.no_item": "Hold venligst en genstand i din hovedhånd og prøv igen." + "command.nova.show_item_behaviors.no_item": "Hold venligst en genstand i din hovedhånd og prøv igen.", + "command.nova.recalculate_leave_properties.done": "Bearbejdede %s blade og gjorde %s blade ikke-vedvarende.", + "command.nova.show_vanilla_block_state.success": "Du ser på bloktilstanden %s.", + "command.nova.show_vanilla_block_state.failure": "Se venligst på en blok og prøv igen." } diff --git a/nova/src/main/resources/assets/nova/lang/de_at.json b/nova/src/main/resources/assets/nova/lang/de_at.json index b9558213a91..64e18d20eb2 100644 --- a/nova/src/main/resources/assets/nova/lang/de_at.json +++ b/nova/src/main/resources/assets/nova/lang/de_at.json @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "Unbekanntes Element Filter", "command.nova.search_block.result": "%s bei %s gefunden.", "command.nova.show_item_behaviors.success": "Dieser %s hat %s Gegenstand Verhalten(e):\n%s", - "command.nova.show_item_behaviors.no_item": "Bitte halten Sie einen Gegenstand in Ihrer Haupthand und versuchen Sie es erneut." + "command.nova.show_item_behaviors.no_item": "Bitte halten Sie einen Gegenstand in Ihrer Haupthand und versuchen Sie es erneut.", + "command.nova.recalculate_leave_properties.done": "Verarbeitete %s Blätter und machte %s Blätter nicht-persistent.", + "command.nova.show_vanilla_block_state.success": "Du betrachtest gerade den Blockstatus %s.", + "command.nova.show_vanilla_block_state.failure": "Bitte schau dir einen Block an und versuche es erneut." } diff --git a/nova/src/main/resources/assets/nova/lang/de_ch.json b/nova/src/main/resources/assets/nova/lang/de_ch.json index 2ecda1324da..719b7b36ac4 100644 --- a/nova/src/main/resources/assets/nova/lang/de_ch.json +++ b/nova/src/main/resources/assets/nova/lang/de_ch.json @@ -161,5 +161,8 @@ "block.nova.unknown": "Unbekannter Block", "command.nova.reload_configs.none": "Es wurden keine Konfigurationen geändert.", "command.nova.show_item_behaviors.success": "Dieser %s hat %s Gegenstand Verhalten(e):\n%s", - "command.nova.show_item_behaviors.no_item": "Bitte halten Sie einen Gegenstand in Ihrer Haupthand und versuchen Sie es erneut." + "command.nova.show_item_behaviors.no_item": "Bitte halten Sie einen Gegenstand in Ihrer Haupthand und versuchen Sie es erneut.", + "command.nova.recalculate_leave_properties.done": "Verarbeitete %s Blätter und machte %s Blätter nicht-persistent.", + "command.nova.show_vanilla_block_state.success": "Du betrachtest gerade den Blockstatus %s.", + "command.nova.show_vanilla_block_state.failure": "Bitte schau dir einen Block an und versuche es erneut." } diff --git a/nova/src/main/resources/assets/nova/lang/de_de.json b/nova/src/main/resources/assets/nova/lang/de_de.json index f5e0751b189..ac2a24200b6 100644 --- a/nova/src/main/resources/assets/nova/lang/de_de.json +++ b/nova/src/main/resources/assets/nova/lang/de_de.json @@ -1,7 +1,7 @@ { "menu.nova.items.search": "Item suchen", "menu.nova.items.search.back": "Zurück zum Hauptinventar", - "menu.nova.items.search.clear": "Suchfeld leeren", + "menu.nova.items.search.clear": "Suche leeren", "menu.nova.items.search-item": "Suchen...", "menu.nova.items.category.misc": "Sonstiges", "menu.nova.recipe": "Handwerksbuch", @@ -43,7 +43,7 @@ "command.nova.no-item-in-hand": "Halte ein Item in deiner Hand und versuche es erneut.", "command.nova.give.success": "%s %s an %s gegeben", "command.nova.list_nearby.success": "Von den %s Rüstungsständern in deinem Chunk sind %s Teil einer TileEntity.", - "command.nova.get_nearest_data.failed": "Es konnte keine TileEntity in deiner Nähe gefunden werden.", + "command.nova.get_nearest_data.failed": "Es konnte keine Tile-Entität in deiner Nähe gefunden werden.", "command.nova.remove_obsolete_models.success": "%s Rüstungsständer wurden entfernt.", "command.nova.remove_tile_entities.success": "%s TileEntites wurden entfernt.", "command.nova.render_distance": "Deine Renderdistanz wurde auf %s gesetzt.", @@ -68,7 +68,7 @@ "command.nova.resource_pack.create.start": "Ressourcenpaket wird erstellt... bitte warten", "menu.nova.items": "Items", "command.nova.resource_pack.reupload.success": "Ressourcenpaket auf %s hochgeladen.", - "command.nova.resource_pack.create.success": "Ressourcenpaket erstellt. Ein Server Neustart ist empfohlen.", + "command.nova.resource_pack.create.success": "Ressourcenpaket erstellt. Ein Neustart des Servers ist empfohlen.", "command.nova.resource_pack.reupload.start": "Ressourcenpaket wird hochgeladen... Bitte warten", "command.nova.resource_pack.reupload.fail": "Das Ressourcenpaket konnte nicht hochgeladen werden.", "menu.nova.side_config.north": "Norden", @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "Unbekannter Itemfilter", "item.nova.unknown_item_filter.description": "Das Addon, das diese Itemfilterart verwaltet, ist entweder nicht vorhanden oder hat ihn nicht registriert.", "command.nova.show_item_behaviors.success": "Dieser %s hat %s Gegenstand Verhalten(e):\n%s", - "command.nova.show_item_behaviors.no_item": "Dieser %s hat %s Gegenstand Verhalten(e):\n%s" + "command.nova.show_item_behaviors.no_item": "Bitte halte ein Item in deiner Hand und versuche es erneut.", + "command.nova.recalculate_leave_properties.done": "Es wurden %s Laubblöcke verarbeitet, wovon %s Laubblöcke non-persistent gemacht wurden.", + "command.nova.show_vanilla_block_state.success": "Du betrachtest gerade den Blockstatus %s.", + "command.nova.show_vanilla_block_state.failure": "Bitte schau dir einen Block an und versuche es erneut." } diff --git a/nova/src/main/resources/assets/nova/lang/el_gr.json b/nova/src/main/resources/assets/nova/lang/el_gr.json index 58974b4c958..d54117e510e 100644 --- a/nova/src/main/resources/assets/nova/lang/el_gr.json +++ b/nova/src/main/resources/assets/nova/lang/el_gr.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.energy.on": "Ενεργοποιήθηκε η προβολή εντοπισμού σφαλμάτων για το Energy-Networks.", "command.nova.show_network_node_info.bridge.networks.entry": "Τύπος: %s, ID: %s", "command.nova.show_item_behaviors.success": "Αυτό το %s έχει %s συμπεριφορά(ες) στοιχείου:\n%s", - "command.nova.show_item_behaviors.no_item": "Κρατήστε ένα αντικείμενο στο κύριο χέρι σας και προσπαθήστε ξανά." + "command.nova.show_item_behaviors.no_item": "Κρατήστε ένα αντικείμενο στο κύριο χέρι σας και προσπαθήστε ξανά.", + "command.nova.recalculate_leave_properties.done": "Επεξεργάστηκε %s φύλλα και κατέστησε %s φύλλα μη μόνιμα.", + "command.nova.show_vanilla_block_state.success": "Βλέπετε την κατάσταση του μπλοκ %s.", + "command.nova.show_vanilla_block_state.failure": "Παρακαλώ κοιτάξτε ένα μπλοκ και προσπαθήστε ξανά." } diff --git a/nova/src/main/resources/assets/nova/lang/en_au.json b/nova/src/main/resources/assets/nova/lang/en_au.json index 717c186d41e..b1eb33c8618 100644 --- a/nova/src/main/resources/assets/nova/lang/en_au.json +++ b/nova/src/main/resources/assets/nova/lang/en_au.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Failed to reload recipes, check the console for more information.", "command.nova.network_debug.nova.energy.on": "Enabled debug-view for Energy-Networks.", "command.nova.show_item_behaviors.success": "This %s has %s item behavior(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again." + "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again.", + "command.nova.recalculate_leave_properties.done": "Processed %s leaves and made %s leaves non-persistent.", + "command.nova.show_vanilla_block_state.success": "You're looking at the block state %s.", + "command.nova.show_vanilla_block_state.failure": "Please look at a block and try again." } diff --git a/nova/src/main/resources/assets/nova/lang/en_ca.json b/nova/src/main/resources/assets/nova/lang/en_ca.json index 0a76895b849..fbf864b33bd 100644 --- a/nova/src/main/resources/assets/nova/lang/en_ca.json +++ b/nova/src/main/resources/assets/nova/lang/en_ca.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Failed to reload recipes, check the console for more information.", "block.nova.unknown": "Unknown Block", "command.nova.show_item_behaviors.success": "This %s has %s item behavior(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again." + "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again.", + "command.nova.recalculate_leave_properties.done": "Processed %s leaves and made %s leaves non-persistent.", + "command.nova.show_vanilla_block_state.success": "You're looking at the block state %s.", + "command.nova.show_vanilla_block_state.failure": "Please look at a block and try again." } diff --git a/nova/src/main/resources/assets/nova/lang/en_gb.json b/nova/src/main/resources/assets/nova/lang/en_gb.json index e3a3b22cad5..9f2a60eb5dd 100644 --- a/nova/src/main/resources/assets/nova/lang/en_gb.json +++ b/nova/src/main/resources/assets/nova/lang/en_gb.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.item.off": "Disabled debug-view for Item-Networks.", "command.nova.search_block.result": "Found %s at %s.", "command.nova.show_item_behaviors.success": "This %s has %s item behavior(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again." + "command.nova.show_item_behaviors.no_item": "Please hold an item in your main hand and try again.", + "command.nova.recalculate_leave_properties.done": "Processed %s leaves and made %s leaves non-persistent.", + "command.nova.show_vanilla_block_state.success": "You're looking at the block state %s.", + "command.nova.show_vanilla_block_state.failure": "Please look at a block and try again." } diff --git a/nova/src/main/resources/assets/nova/lang/en_nz.json b/nova/src/main/resources/assets/nova/lang/en_nz.json index c243e125d20..0f50b6cc16b 100644 --- a/nova/src/main/resources/assets/nova/lang/en_nz.json +++ b/nova/src/main/resources/assets/nova/lang/en_nz.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.energy.on": "Enabled debug-view for Energy-Networks.", "command.nova.show_network_node_info.node": "Name: %s\nworld: %s, x: %s, y: %s, z: %s", "command.nova.show_item_behaviors.success": "This %s has %s item behavior(s):\n%s", - "command.nova.show_item_behaviors.no_item": "This %s has %s item behavior(s):\n%s" + "command.nova.show_item_behaviors.no_item": "This %s has %s item behavior(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Processed %s leaves and made %s leaves non-persistent.", + "command.nova.show_vanilla_block_state.success": "You're looking at the block state %s.", + "command.nova.show_vanilla_block_state.failure": "Please look at a block and try again." } diff --git a/nova/src/main/resources/assets/nova/lang/en_us.json b/nova/src/main/resources/assets/nova/lang/en_us.json index 136a40dcf61..611a6f6cae7 100644 --- a/nova/src/main/resources/assets/nova/lang/en_us.json +++ b/nova/src/main/resources/assets/nova/lang/en_us.json @@ -88,6 +88,8 @@ "command.nova.show_block_data.vanilla_block": "You're looking at the vanilla block %s.", "command.nova.show_block_data.vanilla_tile_entity": "You're looking at the vanilla tile-entity %s. It has the following Nova data:\n%s", "command.nova.show_block_data.failure": "Please look at a block and try again.", + "command.nova.show_vanilla_block_state.success": "You're looking at the block state %s.", + "command.nova.show_vanilla_block_state.failure": "Please look at a block and try again.", "command.nova.show_block_model_data.model_less": "This %s uses the following vanilla block: %s.", "command.nova.show_block_model_data.backing_state": "This %s uses the following backing state configuration:\nBlock: %s\nVariant: %s", "command.nova.show_block_model_data.display_entity": "This %s uses the following display entity configuration:\nHitbox type: %s\nModels (%s):\n%s", @@ -120,6 +122,7 @@ "command.nova.copy_clientside_stack.success": "Added a client-side copy of %s to your inventory.", "command.nova.search_block.result": "Found %s at %s.", "command.nova.search_block.done": "Search completed.", + "command.nova.recalculate_leave_properties.done": "Processed %s leaves and made %s leaves non-persistent.", "command.nova.recipe.no-recipe": "This item does not have a recipe.", "command.nova.usage.no-usage": "This item has no usages.", "command.nova.network_reload.success": "All active networks have been reloaded.", diff --git a/nova/src/main/resources/assets/nova/lang/es_ar.json b/nova/src/main/resources/assets/nova/lang/es_ar.json index cf116abf38e..fddb73f21ad 100644 --- a/nova/src/main/resources/assets/nova/lang/es_ar.json +++ b/nova/src/main/resources/assets/nova/lang/es_ar.json @@ -161,5 +161,8 @@ "command.nova.reload_configs.failure": "No se ha podido recargar la configuración, compruebe la consola para obtener más información.", "command.nova.reload_recipes.failure": "Fallo al recargar recetas, compruebe la consola para más información.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Por favor, sostenga un objeto en su mano principal e inténtelo de nuevo." + "command.nova.show_item_behaviors.no_item": "Por favor, sostenga un objeto en su mano principal e inténtelo de nuevo.", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_cl.json b/nova/src/main/resources/assets/nova/lang/es_cl.json index b245466f23b..882afca9ae5 100644 --- a/nova/src/main/resources/assets/nova/lang/es_cl.json +++ b/nova/src/main/resources/assets/nova/lang/es_cl.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Fallo al recargar recetas, compruebe la consola para más información.", "command.nova.search_block.result": "Encontrado %s en %s.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Por favor, sostenga un objeto en su mano principal e inténtelo de nuevo." + "command.nova.show_item_behaviors.no_item": "Por favor, sostenga un objeto en su mano principal e inténtelo de nuevo.", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_ec.json b/nova/src/main/resources/assets/nova/lang/es_ec.json index 3f8d86d2697..a4cce87b48c 100644 --- a/nova/src/main/resources/assets/nova/lang/es_ec.json +++ b/nova/src/main/resources/assets/nova/lang/es_ec.json @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "Filtro de artículos desconocidos", "command.nova.fill.success": "Rellenado con éxito el área desde x: %s, y: %s, z: %s hasta x: %s, y: %s, z: %s con %s.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s" + "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_es.json b/nova/src/main/resources/assets/nova/lang/es_es.json index a4bb033e536..ebaec2f7868 100644 --- a/nova/src/main/resources/assets/nova/lang/es_es.json +++ b/nova/src/main/resources/assets/nova/lang/es_es.json @@ -1,10 +1,10 @@ { - "menu.nova.items.search": "Buscar un artículo", + "menu.nova.items.search": "Buscar un objeto", "menu.nova.items.search.back": "Regresar al inventario principal", "menu.nova.items.search.clear": "Limpiar búsqueda", - "menu.nova.items.search-item": "Buscando...", + "menu.nova.items.search-item": "Buscar...", "menu.nova.items.category.misc": "Varios", - "menu.nova.recipe": "Guía de Trabajo", + "menu.nova.recipe": "Guía de Crafteo", "menu.nova.recipe.back": "Atrás", "menu.nova.recipe.item_info": "Información de Ítem", "menu.nova.recipe.time": "Tiempo: %ss", @@ -43,7 +43,7 @@ "command.nova.no-players": "No se ha encontrado ningún jugador.", "command.nova.no-item-in-hand": "Mantén un item en tu mano e inténtalo de nuevo.", "command.nova.give.success": "Otorgó %s %s a %s", - "command.nova.list_nearby.success": "Entre los soportes de armadura %s en el bloque en el que te encuentras, %s son entidades de bloque.", + "command.nova.list_nearby.success": "Entre los %s soportes de armadura en el chunk en el que te encuentras, %s son entidades de bloque.", "command.nova.get_nearest_data.failed": "No se pudo encontrar una entidad cercana.", "command.nova.remove_obsolete_models.success": "Se han eliminado %s soportes de armadura.", "command.nova.remove_tile_entities.success": "Se han eliminado %s entidades.", @@ -53,7 +53,7 @@ "command.nova.modeldata.no-nova-item": "Sujeta un item de Nova en tu mano e inténtalo de nuevo.", "command.nova.recipe.no-recipe": "Este item no tiene una receta.", "command.nova.usage.no-usage": "Este item no tiene ningún uso.", - "menu.nova.items": "Artículos", + "menu.nova.items": "Objetos", "command.nova.network_reload.success": "Se han recargado todas las redes activas.", "command.nova.resource_pack.create.start": "Creando paquete de recursos... por favor espere", "command.nova.resource_pack.create.success": "Paquete de recursos creado. Se recomienda reiniciar el servidor.", @@ -74,9 +74,9 @@ "command.nova.invalidate_char_sizes.success": "Invalidado con éxito todos los tamaños char. Se volverán a calcular durante la próxima creación del paquete de recursos.", "command.nova.reload_configs.start": "Recargando configuraciones...", "item.cbf_tags": "CBF: %s etiqueta(s)", - "menu.nova.items.cheat_mode.on": "Modo Trampa: En", - "menu.nova.items.cheat_mode.off": "Modo Trampa: Off", - "menu.nova.side_config.top": "Top", + "menu.nova.items.cheat_mode.on": "Trucos: Sí", + "menu.nova.items.cheat_mode.off": "Trucos: No", + "menu.nova.side_config.top": "Encima", "menu.nova.side_config.north": "Norte", "menu.nova.side_config.east": "Este", "menu.nova.side_config.south": "Sur", @@ -111,7 +111,7 @@ "nova.tile_entity_limits.amount_global.deny": "No se pueden colocar más fichas-entidades de este tipo.", "nova.tile_entity_limits.amount_per_chunk_total.deny": "No se pueden colocar más fichas-entidades en este trozo.", "nova.outdated_version": "Está ejecutando una versión obsoleta de %s.\nDescargue la última versión en %s.", - "item.nova.unknown_item_filter": "Filtro de artículos desconocidos", + "item.nova.unknown_item_filter": "Filtro de objetos desconocidos", "item.nova.unknown_item_filter.description": "El addon que gestiona este tipo de filtro de elementos no está presente o no lo ha registrado.", "block.nova.unknown": "Bloque desconocido", "block.nova.unknown.message": "Ningún addon ha registrado este estado de bloque: ID: %s, Serializado: %s", @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Fallo al recargar recetas, compruebe la consola para más información.", "command.nova.show_item_model_data.success": "Este %s utiliza el tipo de artículo %s con el modelo %s.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s" + "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_mx.json b/nova/src/main/resources/assets/nova/lang/es_mx.json index 4bf8ff02ddb..d3b8a9d2f86 100644 --- a/nova/src/main/resources/assets/nova/lang/es_mx.json +++ b/nova/src/main/resources/assets/nova/lang/es_mx.json @@ -161,5 +161,8 @@ "command.nova.give_clientside_stack.success": "Añadida una versión cliente de %s a tu inventario.", "command.nova.copy_clientside_stack.success": "Añadida una copia del lado del cliente de %s a tu inventario.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s" + "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_uy.json b/nova/src/main/resources/assets/nova/lang/es_uy.json index 2b7d642928a..3fff59729f1 100644 --- a/nova/src/main/resources/assets/nova/lang/es_uy.json +++ b/nova/src/main/resources/assets/nova/lang/es_uy.json @@ -161,5 +161,8 @@ "block.nova.unknown.message": "Ningún addon ha registrado este estado de bloque: ID: %s, Serializado: %s", "command.nova.reload_configs.none": "No se ha modificado ninguna configuración.", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s" + "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/es_ve.json b/nova/src/main/resources/assets/nova/lang/es_ve.json index 43f437beb85..49beb48a437 100644 --- a/nova/src/main/resources/assets/nova/lang/es_ve.json +++ b/nova/src/main/resources/assets/nova/lang/es_ve.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.energy.on": "Activada la vista de depuración para redes de energía.", "command.nova.show_network_node_info.connected_nodes.header": "Conexiones (%s):", "command.nova.show_item_behaviors.success": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", - "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s" + "command.nova.show_item_behaviors.no_item": "Este %s tiene %s comportamiento(s) de elemento(s):\n%s", + "command.nova.recalculate_leave_properties.done": "Procesadas %s hojas y hechas %s hojas no persistentes.", + "command.nova.show_vanilla_block_state.success": "Estás viendo el estado del bloque %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, mira un bloque e inténtalo de nuevo." } diff --git a/nova/src/main/resources/assets/nova/lang/et_ee.json b/nova/src/main/resources/assets/nova/lang/et_ee.json index ce9e5d87c90..f89f4e687ec 100644 --- a/nova/src/main/resources/assets/nova/lang/et_ee.json +++ b/nova/src/main/resources/assets/nova/lang/et_ee.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Retseptide ümberlaadimine ebaõnnestus, kontrollige konsooli lisateavet.", "command.nova.show_network_node_info.initialized": "Võrk initsialiseeritud: %s", "command.nova.show_item_behaviors.success": "Sellel %s on %s elemendi käitumine(d):\n%s", - "command.nova.show_item_behaviors.no_item": "Palun hoidke eset oma põhikäes ja proovige uuesti." + "command.nova.show_item_behaviors.no_item": "Palun hoidke eset oma põhikäes ja proovige uuesti.", + "command.nova.recalculate_leave_properties.done": "Töötlesid %s lehte ja muutsid %s lehed mittepüsivaks.", + "command.nova.show_vanilla_block_state.success": "Sa vaatad ploki olekut %s.", + "command.nova.show_vanilla_block_state.failure": "Palun vaadake plokki ja proovige uuesti." } diff --git a/nova/src/main/resources/assets/nova/lang/fi_fi.json b/nova/src/main/resources/assets/nova/lang/fi_fi.json index 88edd799314..99399b7702b 100644 --- a/nova/src/main/resources/assets/nova/lang/fi_fi.json +++ b/nova/src/main/resources/assets/nova/lang/fi_fi.json @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "Tuntematon kohde suodatin", "command.nova.search_block.done": "Haku suoritettu.", "command.nova.show_item_behaviors.success": "Tällä %s:llä on %s kohteen käyttäytymistä:\n%s", - "command.nova.show_item_behaviors.no_item": "Pidä esinettä pääkädessäsi ja yritä uudelleen." + "command.nova.show_item_behaviors.no_item": "Pidä esinettä pääkädessäsi ja yritä uudelleen.", + "command.nova.recalculate_leave_properties.done": "Käsitteli %s lehteä ja teki %s lehdestä ei-pysyviä.", + "command.nova.show_vanilla_block_state.success": "Katsot lohkon tilaa %s.", + "command.nova.show_vanilla_block_state.failure": "Katso lohkoa ja yritä uudelleen." } diff --git a/nova/src/main/resources/assets/nova/lang/fr_ca.json b/nova/src/main/resources/assets/nova/lang/fr_ca.json index 86bffc72aea..2d900301b2f 100644 --- a/nova/src/main/resources/assets/nova/lang/fr_ca.json +++ b/nova/src/main/resources/assets/nova/lang/fr_ca.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Échec du rechargement des recettes, vérifiez la console pour plus d'informations.", "command.nova.reload_configs.none": "Aucune configuration n'a été modifiée.", "command.nova.show_item_behaviors.success": "Ce %s a le(s) comportement(s) de l'élément %s :\n%s", - "command.nova.show_item_behaviors.no_item": "Tenez un objet dans votre main principale et réessayez." + "command.nova.show_item_behaviors.no_item": "Tenez un objet dans votre main principale et réessayez.", + "command.nova.recalculate_leave_properties.done": "A traité %s feuilles et a rendu %s feuilles non persistantes.", + "command.nova.show_vanilla_block_state.success": "Vous regardez l'état du bloc %s.", + "command.nova.show_vanilla_block_state.failure": "Veuillez regarder un bloc et réessayer." } diff --git a/nova/src/main/resources/assets/nova/lang/fr_fr.json b/nova/src/main/resources/assets/nova/lang/fr_fr.json index 9a7af38c6a8..b48db4cb518 100644 --- a/nova/src/main/resources/assets/nova/lang/fr_fr.json +++ b/nova/src/main/resources/assets/nova/lang/fr_fr.json @@ -161,5 +161,8 @@ "command.nova.reload_configs.failure": "Échec du rechargement des configurations, vérifiez la console pour plus d'informations.", "command.nova.reload_recipes.failure": "Échec du rechargement des recettes, vérifiez la console pour plus d'informations.", "command.nova.show_item_behaviors.success": "Ce %s a le(s) comportement(s) de l'élément %s :\n%s", - "command.nova.show_item_behaviors.no_item": "Tenez un objet dans votre main principale et réessayez." + "command.nova.show_item_behaviors.no_item": "Tenez un objet dans votre main principale et réessayez.", + "command.nova.recalculate_leave_properties.done": "A traité %s feuilles et a rendu %s feuilles non persistantes.", + "command.nova.show_vanilla_block_state.success": "Vous regardez l'état du bloc %s.", + "command.nova.show_vanilla_block_state.failure": "Veuillez regarder un bloc et réessayer." } diff --git a/nova/src/main/resources/assets/nova/lang/hu_hu.json b/nova/src/main/resources/assets/nova/lang/hu_hu.json index 966363f37cb..36aab87a56b 100644 --- a/nova/src/main/resources/assets/nova/lang/hu_hu.json +++ b/nova/src/main/resources/assets/nova/lang/hu_hu.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Nem sikerült újratölteni a recepteket, további információkért ellenőrizze a konzolt.", "command.nova.show_network_node_info.end_point.networks.entry": "Típus: %s, Irány: %s, Azonosító: %s", "command.nova.show_item_behaviors.success": "Ez a %s %s elem viselkedése(i):\n%s", - "command.nova.show_item_behaviors.no_item": "Kérjük, tartson egy tárgyat a fő kezében, és próbálja meg újra." + "command.nova.show_item_behaviors.no_item": "Kérjük, tartson egy tárgyat a fő kezében, és próbálja meg újra.", + "command.nova.recalculate_leave_properties.done": "Feldolgozott %s levelet, és %s levelet nem tartósított.", + "command.nova.show_vanilla_block_state.success": "A blokk állapotát nézed %s.", + "command.nova.show_vanilla_block_state.failure": "Kérjük, nézz meg egy blokkot, és próbáld meg újra." } diff --git a/nova/src/main/resources/assets/nova/lang/id_id.json b/nova/src/main/resources/assets/nova/lang/id_id.json index 32b257426d0..5cf1719f738 100644 --- a/nova/src/main/resources/assets/nova/lang/id_id.json +++ b/nova/src/main/resources/assets/nova/lang/id_id.json @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "Filter Item Tidak Dikenal", "command.nova.search_block.result": "Ditemukan %s pada %s.", "command.nova.show_item_behaviors.success": "%s ini memiliki perilaku item %s:\n%s", - "command.nova.show_item_behaviors.no_item": "Peganglah sebuah benda di tangan utama Anda dan coba lagi." + "command.nova.show_item_behaviors.no_item": "Peganglah sebuah benda di tangan utama Anda dan coba lagi.", + "command.nova.recalculate_leave_properties.done": "Mengolah daun %s dan membuat daun %s tidak persisten.", + "command.nova.show_vanilla_block_state.success": "Anda sedang melihat status blok %s.", + "command.nova.show_vanilla_block_state.failure": "Silakan lihat blok dan coba lagi." } diff --git a/nova/src/main/resources/assets/nova/lang/it_it.json b/nova/src/main/resources/assets/nova/lang/it_it.json index 807575e7179..0727e9a363b 100644 --- a/nova/src/main/resources/assets/nova/lang/it_it.json +++ b/nova/src/main/resources/assets/nova/lang/it_it.json @@ -161,5 +161,8 @@ "block.nova.unknown": "Blocco sconosciuto", "command.nova.search_block.done": "Ricerca completata.", "command.nova.show_item_behaviors.no_item": "Tenere un oggetto nella mano principale e riprovare.", - "command.nova.show_item_behaviors.success": "Questo %s ha uno o più comportamenti dell'articolo %s:\n%s" + "command.nova.show_item_behaviors.success": "Questo %s ha uno o più comportamenti dell'articolo %s:\n%s", + "command.nova.recalculate_leave_properties.done": "Elaborato %s foglie e reso %s foglie non persistenti.", + "command.nova.show_vanilla_block_state.success": "Stai guardando lo stato del blocco %s.", + "command.nova.show_vanilla_block_state.failure": "Guardare un blocco e riprovare." } diff --git a/nova/src/main/resources/assets/nova/lang/ja_jp.json b/nova/src/main/resources/assets/nova/lang/ja_jp.json index b7861b9148e..3cf204448db 100644 --- a/nova/src/main/resources/assets/nova/lang/ja_jp.json +++ b/nova/src/main/resources/assets/nova/lang/ja_jp.json @@ -161,5 +161,8 @@ "block.nova.unknown": "不明ブロック", "command.nova.reload_configs.none": "コンフィグは変更されていない。", "command.nova.show_item_behaviors.success": "この %s は %s 項目の動作を持っています:\n%s", - "command.nova.show_item_behaviors.no_item": "本手にアイテムを持って、もう一度試してください。" + "command.nova.show_item_behaviors.no_item": "本手にアイテムを持って、もう一度試してください。", + "command.nova.recalculate_leave_properties.done": "s 個の葉を処理し、%s 個の葉を非永続にしました。", + "command.nova.show_vanilla_block_state.success": "ブロックの状態 %s を見ています。", + "command.nova.show_vanilla_block_state.failure": "ブロックを見て、もう一度試してください。" } diff --git a/nova/src/main/resources/assets/nova/lang/ko_kr.json b/nova/src/main/resources/assets/nova/lang/ko_kr.json index a86291d5e5d..12ad0f568d7 100644 --- a/nova/src/main/resources/assets/nova/lang/ko_kr.json +++ b/nova/src/main/resources/assets/nova/lang/ko_kr.json @@ -161,5 +161,8 @@ "item.nova.unknown_item_filter": "알 수 없는 항목 필터", "command.nova.copy_clientside_stack.success": "인벤토리에 %s의 클라이언트 측 복사본을 추가했습니다.", "command.nova.show_item_behaviors.no_item": "주 손에 아이템을 들고 다시 시도하세요.", - "command.nova.show_item_behaviors.success": "이 %s에는 %s 항목 동작이 있습니다:\n%s" + "command.nova.show_item_behaviors.success": "이 %s에는 %s 항목 동작이 있습니다:\n%s", + "command.nova.recalculate_leave_properties.done": "잎을 처리하고 %s 잎을 비영구적으로 만들었습니다.", + "command.nova.show_vanilla_block_state.success": "블록 상태 %s를 보고 있습니다.", + "command.nova.show_vanilla_block_state.failure": "블록을 보고 다시 시도하세요." } diff --git a/nova/src/main/resources/assets/nova/lang/lt_lt.json b/nova/src/main/resources/assets/nova/lang/lt_lt.json index c09a55add44..e0dcff79076 100644 --- a/nova/src/main/resources/assets/nova/lang/lt_lt.json +++ b/nova/src/main/resources/assets/nova/lang/lt_lt.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Nepavyko iš naujo įkelti receptų, daugiau informacijos ieškokite konsolėje.", "command.nova.show_network_node_info.end_point.networks.entry": "Tipas: %s, kryptis: %s, ID: %s", "command.nova.show_item_behaviors.success": "Šis %s turi %s elemento elgseną (-as):\n%s", - "command.nova.show_item_behaviors.no_item": "Laikykite daiktą pagrindinėje rankoje ir bandykite dar kartą." + "command.nova.show_item_behaviors.no_item": "Laikykite daiktą pagrindinėje rankoje ir bandykite dar kartą.", + "command.nova.recalculate_leave_properties.done": "Apdorota %s lapų ir %s lapų tapo nepastovūs.", + "command.nova.show_vanilla_block_state.success": "Jūs žiūrite į bloko būseną %s.", + "command.nova.show_vanilla_block_state.failure": "Pažvelkite į bloką ir pabandykite dar kartą." } diff --git a/nova/src/main/resources/assets/nova/lang/lv_lv.json b/nova/src/main/resources/assets/nova/lang/lv_lv.json index eea690b80f1..c52d8f1f4c4 100644 --- a/nova/src/main/resources/assets/nova/lang/lv_lv.json +++ b/nova/src/main/resources/assets/nova/lang/lv_lv.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Receptes neizdevās ielādēt, pārbaudiet konsoles ekrānā papildu informāciju.", "item.nova.unknown_item_filter": "Nezināms vienuma filtrs", "command.nova.show_item_behaviors.success": "Šim %s ir %s elementu uzvedība(-as):\n%s", - "command.nova.show_item_behaviors.no_item": "Lūdzu, paturiet priekšmetu galvenajā rokā un mēģiniet vēlreiz." + "command.nova.show_item_behaviors.no_item": "Lūdzu, paturiet priekšmetu galvenajā rokā un mēģiniet vēlreiz.", + "command.nova.recalculate_leave_properties.done": "Apstrādāti %s lapu un %s lapas padarītas nepastāvīgas.", + "command.nova.show_vanilla_block_state.success": "Jūs skatāties uz bloka stāvokli %s.", + "command.nova.show_vanilla_block_state.failure": "Lūdzu, apskatiet bloku un mēģiniet vēlreiz." } diff --git a/nova/src/main/resources/assets/nova/lang/nl_be.json b/nova/src/main/resources/assets/nova/lang/nl_be.json index c3da7f8f7bb..c3ac6150fba 100644 --- a/nova/src/main/resources/assets/nova/lang/nl_be.json +++ b/nova/src/main/resources/assets/nova/lang/nl_be.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Het opnieuw laden van recepten is mislukt, controleer de console voor meer informatie.", "command.nova.show_network_node_info.bridge.networks.entry": "Type: %s, ID: %s", "command.nova.show_item_behaviors.success": "Deze %s heeft %s item gedrag(en):\n%s", - "command.nova.show_item_behaviors.no_item": "Houd een voorwerp in je hoofdhand en probeer het opnieuw." + "command.nova.show_item_behaviors.no_item": "Houd een voorwerp in je hoofdhand en probeer het opnieuw.", + "command.nova.recalculate_leave_properties.done": "Verwerkte %s bladeren en maakte %s bladeren niet-persistent.", + "command.nova.show_vanilla_block_state.success": "Je kijkt naar de blokstatus %s.", + "command.nova.show_vanilla_block_state.failure": "Kijk naar een blok en probeer het opnieuw." } diff --git a/nova/src/main/resources/assets/nova/lang/nl_nl.json b/nova/src/main/resources/assets/nova/lang/nl_nl.json index a19b194e693..f335a98e612 100644 --- a/nova/src/main/resources/assets/nova/lang/nl_nl.json +++ b/nova/src/main/resources/assets/nova/lang/nl_nl.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Het opnieuw laden van recepten is mislukt, controleer de console voor meer informatie.", "command.nova.reload_configs.none": "Er zijn geen configuraties gewijzigd.", "command.nova.show_item_behaviors.success": "Deze %s heeft %s item gedrag(en):\n%s", - "command.nova.show_item_behaviors.no_item": "Houd een voorwerp in je hoofdhand en probeer het opnieuw." + "command.nova.show_item_behaviors.no_item": "Houd een voorwerp in je hoofdhand en probeer het opnieuw.", + "command.nova.recalculate_leave_properties.done": "Verwerkte %s bladeren en maakte %s bladeren niet-persistent.", + "command.nova.show_vanilla_block_state.success": "Je kijkt naar de blokstatus %s.", + "command.nova.show_vanilla_block_state.failure": "Kijk naar een blok en probeer het opnieuw." } diff --git a/nova/src/main/resources/assets/nova/lang/no_no.json b/nova/src/main/resources/assets/nova/lang/no_no.json index fd5b7f583b1..06898810132 100644 --- a/nova/src/main/resources/assets/nova/lang/no_no.json +++ b/nova/src/main/resources/assets/nova/lang/no_no.json @@ -161,5 +161,8 @@ "enchantment.nova.unknown": "Ukjent fortryllelse: %s", "command.nova.search_block.result": "Fant %s på %s.", "command.nova.show_item_behaviors.success": "Denne %s har %s elementatferd(er):\n%s", - "command.nova.show_item_behaviors.no_item": "Hold en gjenstand i hovedhånden og prøv igjen." + "command.nova.show_item_behaviors.no_item": "Hold en gjenstand i hovedhånden og prøv igjen.", + "command.nova.recalculate_leave_properties.done": "Behandlet %s blader og gjort %s blader ikke-vedvarende.", + "command.nova.show_vanilla_block_state.success": "Du ser på blokktilstanden %s.", + "command.nova.show_vanilla_block_state.failure": "Vennligst se på en blokk og prøv igjen." } diff --git a/nova/src/main/resources/assets/nova/lang/pl_pl.json b/nova/src/main/resources/assets/nova/lang/pl_pl.json index 9217d465cef..9dab8aefc15 100644 --- a/nova/src/main/resources/assets/nova/lang/pl_pl.json +++ b/nova/src/main/resources/assets/nova/lang/pl_pl.json @@ -31,15 +31,15 @@ "menu.nova.visual_region.hide": "Ukryj obszar", "menu.nova.visual_region.show": "Pokaż obszar", "inventory.nova.default": "Ekwipunek", - "inventory.nova.input": "Zapasy wejściowe", - "inventory.nova.output": "Zapasy wyjściowe", + "inventory.nova.input": "Wejściowy ekwipunek", + "inventory.nova.output": "Wyjściowy ekwipunek", "container.nova.fluid_tank": "Zbiornik", "container.nova.lava_tank": "Zbiornik Lawy", "container.nova.water_tank": "Zbiornik Wody", - "command.nova.list_nearby.success": "Spośród %s stoisk zbroi w twoim chunku, %s stanowią część kafelka.", - "command.nova.get_nearest_data.failed": "Nie można znaleźć obiektu w pobliżu.", + "command.nova.list_nearby.success": "Spośród %s stojaków na zbroję w twoim chunku, %s to tile entity.", + "command.nova.get_nearest_data.failed": "Nie można znaleźć tile entity w pobliżu.", "command.nova.remove_obsolete_models.success": "Usunięto %s stojaków na zbroję.", - "command.nova.remove_tile_entities.success": "Usunięto %s obiektów.", + "command.nova.remove_tile_entities.success": "Usunięto %s tile entity.", "command.nova.render_distance": "Odległość renderowania została ustawiona na %s.", "waila.nova.required_tool": "Narzędzie: ", "waila.nova.required_tool.none": "Nie jest wymagane żadne narzędzie", @@ -53,7 +53,7 @@ "nova.tile_entity_limits.type_blacklist.deny": "Nie możesz umieszczać żadnych kafelków tego typu.", "menu.nova.region.increase": "Rozwiń region", "menu.nova.region.decrease": "Zmniejsz region", - "inventory.nova.all": "Wszystkie zapasy", + "inventory.nova.all": "Wszystkie ekwipunki", "command.nova.remove_invalid_vtes.success": "Pomyślnie usunięto %s nieprawidłowych elementów kafelków wanilii.", "nova.tile_entity_limits.amount_global.deny": "Nie można umieścić więcej kafelków tego typu.", "nova.tile_entity_limits.amount_per_chunk.deny": "Nie można umieścić więcej kafelków tego typu w tym fragmencie.", @@ -115,14 +115,14 @@ "block.nova.unknown": "Nieznany blok", "block.nova.unknown.message": "Żaden dodatek nie zarejestrował tego stanu bloku: ID: %s, Serialized: %s", "enchantment.nova.unknown": "Nieznane zaklęcie: %s", - "command.nova.network_debug.nova.energy.on": "Włączony widok debugowania dla Energy-Networks.", - "command.nova.network_debug.nova.energy.off": "Wyłączony widok debugowania dla Energy-Networks.", - "command.nova.network_debug.nova.item.on": "Włączony widok debugowania dla Item-Networks.", - "command.nova.network_debug.nova.item.off": "Wyłączony widok debugowania dla Item-Networks.", - "command.nova.network_debug.nova.fluid.on": "Włączony widok debugowania dla Fluid-Networks.", - "command.nova.network_debug.nova.fluid.off": "Wyłączony widok debugowania dla Fluid-Networks.", - "command.nova.network_cluster_debug.on": "Włączony widok debugowania dla klastrów sieciowych.", - "command.nova.network_cluster_debug.off": "Wyłączony widok debugowania dla klastrów sieciowych.", + "command.nova.network_debug.nova.energy.on": "Włączono widok debugowania dla sieci energii.", + "command.nova.network_debug.nova.energy.off": "Wyłączono widok debugowania dla sieci energii.", + "command.nova.network_debug.nova.item.on": "Włączono widok debugowania dla sieci przedmiotów.", + "command.nova.network_debug.nova.item.off": "Wyłączono widok debugowania dla sieci przedmiotów.", + "command.nova.network_debug.nova.fluid.on": "Włączono widok debugowania dla sieci cieczy.", + "command.nova.network_debug.nova.fluid.off": "Wyłączono widok debugowania dla sieci cieczy.", + "command.nova.network_cluster_debug.on": "Włączono widok debugowania dla klastrów sieciowych.", + "command.nova.network_cluster_debug.off": "Wyłączono widok debugowania dla klastrów sieciowych.", "command.nova.reregister_network_nodes.success": "Pomyślnie ponownie zarejestrowano %s węzłów sieci.", "command.nova.show_block_data.nova_block": "Patrzysz na nie-płytkowy blok Nova %s.", "command.nova.show_block_data.nova_tile_entity": "Patrzysz na kafelek Nova %s. Ma on następujące dane:\n%s", @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Nie udało się przeładować receptur, sprawdź konsolę, aby uzyskać więcej informacji.", "item.nova.unknown_item_filter": "Filtr nieznanych przedmiotów", "command.nova.show_item_behaviors.success": "Ten %s ma %s zachowań elementów:\n%s", - "command.nova.show_item_behaviors.no_item": "Przytrzymaj przedmiot w głównej ręce i spróbuj ponownie." + "command.nova.show_item_behaviors.no_item": "Przytrzymaj przedmiot w głównej ręce i spróbuj ponownie.", + "command.nova.recalculate_leave_properties.done": "Przetworzono %s liści i uczyniono %s liści nietrwałymi.", + "command.nova.show_vanilla_block_state.success": "Patrzysz na stan bloku %s.", + "command.nova.show_vanilla_block_state.failure": "Spójrz na blok i spróbuj ponownie." } diff --git a/nova/src/main/resources/assets/nova/lang/pt_br.json b/nova/src/main/resources/assets/nova/lang/pt_br.json index 0de019075bb..7cb4745fe27 100644 --- a/nova/src/main/resources/assets/nova/lang/pt_br.json +++ b/nova/src/main/resources/assets/nova/lang/pt_br.json @@ -161,5 +161,8 @@ "enchantment.nova.unknown": "Encantamento desconhecido: %s", "command.nova.show_network_node_info.end_point.header": "Esse %s (ponto final) tem os seguintes dados:", "command.nova.show_item_behaviors.success": "Este %s tem %s comportamento(s) de item:\n%s", - "command.nova.show_item_behaviors.no_item": "Segure um item em sua mão principal e tente novamente." + "command.nova.show_item_behaviors.no_item": "Segure um item em sua mão principal e tente novamente.", + "command.nova.recalculate_leave_properties.done": "Processou %s folhas e tornou %s folhas não persistentes.", + "command.nova.show_vanilla_block_state.success": "Você está olhando para o estado do bloco %s.", + "command.nova.show_vanilla_block_state.failure": "Dê uma olhada em um bloco e tente novamente." } diff --git a/nova/src/main/resources/assets/nova/lang/pt_pt.json b/nova/src/main/resources/assets/nova/lang/pt_pt.json index 38e6017296e..6e0e56c1318 100644 --- a/nova/src/main/resources/assets/nova/lang/pt_pt.json +++ b/nova/src/main/resources/assets/nova/lang/pt_pt.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Falha ao recarregar receitas, verifique a consola para mais informações.", "command.nova.show_item_model_data.no_item": "Segure um item Nova na sua mão principal e tente novamente.", "command.nova.show_item_behaviors.success": "Este %s tem %s comportamento(s) de item:\n%s", - "command.nova.show_item_behaviors.no_item": "Segure um objeto na sua mão principal e tente novamente." + "command.nova.show_item_behaviors.no_item": "Segure um objeto na sua mão principal e tente novamente.", + "command.nova.recalculate_leave_properties.done": "Processou %s folhas e tornou %s folhas não persistentes.", + "command.nova.show_vanilla_block_state.success": "Está a olhar para o estado do bloco %s.", + "command.nova.show_vanilla_block_state.failure": "Por favor, olha para um bloco e tenta novamente." } diff --git a/nova/src/main/resources/assets/nova/lang/ro_ro.json b/nova/src/main/resources/assets/nova/lang/ro_ro.json index 29c8fa82ca8..62070d9f80c 100644 --- a/nova/src/main/resources/assets/nova/lang/ro_ro.json +++ b/nova/src/main/resources/assets/nova/lang/ro_ro.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.energy.off": "Dezactivat debug-view pentru Energy-Networks.", "command.nova.show_network_node_info.initialized": "Rețea inițializată: %s", "command.nova.show_item_behaviors.success": "Acest %s are %s comportament(uri) de articol:\n%s", - "command.nova.show_item_behaviors.no_item": "Vă rugăm să țineți un obiect în mâna principală și să încercați din nou." + "command.nova.show_item_behaviors.no_item": "Vă rugăm să țineți un obiect în mâna principală și să încercați din nou.", + "command.nova.recalculate_leave_properties.done": "Procesat %s frunze și făcut %s frunze nepersistente.", + "command.nova.show_vanilla_block_state.success": "Vă uitați la starea blocului %s.", + "command.nova.show_vanilla_block_state.failure": "Vă rugăm să vă uitați la un bloc și să încercați din nou." } diff --git a/nova/src/main/resources/assets/nova/lang/ru_ru.json b/nova/src/main/resources/assets/nova/lang/ru_ru.json index 213306d59c9..412609914f6 100644 --- a/nova/src/main/resources/assets/nova/lang/ru_ru.json +++ b/nova/src/main/resources/assets/nova/lang/ru_ru.json @@ -161,5 +161,8 @@ "command.nova.reload_configs.failure": "Не удалось перезагрузить конфигурацию, проверьте консоль для получения дополнительной информации.", "command.nova.reload_recipes.failure": "Не удалось перезагрузить рецепты, проверьте консоль для получения дополнительной информации.", "command.nova.show_item_behaviors.success": "У этого %s есть %s поведение(и) предмета:\n%s", - "command.nova.show_item_behaviors.no_item": "Пожалуйста, возьмите предмет в основную руку и повторите попытку." + "command.nova.show_item_behaviors.no_item": "Пожалуйста, возьмите предмет в основную руку и повторите попытку.", + "command.nova.recalculate_leave_properties.done": "Обработано %s листьев и сделано %s листьев непостоянными.", + "command.nova.show_vanilla_block_state.success": "Вы смотрите на состояние блока %s.", + "command.nova.show_vanilla_block_state.failure": "Пожалуйста, посмотрите на блок и попробуйте еще раз." } diff --git a/nova/src/main/resources/assets/nova/lang/sk_sk.json b/nova/src/main/resources/assets/nova/lang/sk_sk.json index 322aa57c3fa..ee4285ccbb1 100644 --- a/nova/src/main/resources/assets/nova/lang/sk_sk.json +++ b/nova/src/main/resources/assets/nova/lang/sk_sk.json @@ -161,5 +161,8 @@ "command.nova.reload_recipes.failure": "Nepodarilo sa načítať recepty, ďalšie informácie nájdete v konzole.", "command.nova.network_debug.nova.energy.on": "Povolené ladiace zobrazenie pre Energy-Networks.", "command.nova.show_item_behaviors.success": "Tento %s má %s správanie(-a) položky:\n%s", - "command.nova.show_item_behaviors.no_item": "Podržte predmet v hlavnej ruke a skúste to znova." + "command.nova.show_item_behaviors.no_item": "Podržte predmet v hlavnej ruke a skúste to znova.", + "command.nova.recalculate_leave_properties.done": "Spracovaných %s listov a %s listov sa stalo nepersistentnými.", + "command.nova.show_vanilla_block_state.success": "Pozeráte sa na stav bloku %s.", + "command.nova.show_vanilla_block_state.failure": "Pozrite sa na blok a skúste to znova." } diff --git a/nova/src/main/resources/assets/nova/lang/sl_si.json b/nova/src/main/resources/assets/nova/lang/sl_si.json index 07b2ffd6c4b..1d2b4062ee5 100644 --- a/nova/src/main/resources/assets/nova/lang/sl_si.json +++ b/nova/src/main/resources/assets/nova/lang/sl_si.json @@ -161,5 +161,8 @@ "command.nova.search_block.result": "Najdeno %s na %s.", "command.nova.reload_configs.none": "Konfiguracije niso bile spremenjene.", "command.nova.show_item_behaviors.success": "Ta %s ima %s obnašanja elementov:\n%s", - "command.nova.show_item_behaviors.no_item": "Držite predmet v glavni roki in poskusite znova." + "command.nova.show_item_behaviors.no_item": "Držite predmet v glavni roki in poskusite znova.", + "command.nova.recalculate_leave_properties.done": "Obdelanih je bilo %s listov in %s listov je postalo nepersistentnih.", + "command.nova.show_vanilla_block_state.success": "Gledate stanje bloka %s.", + "command.nova.show_vanilla_block_state.failure": "Oglejte si blok in poskusite znova." } diff --git a/nova/src/main/resources/assets/nova/lang/sv_se.json b/nova/src/main/resources/assets/nova/lang/sv_se.json index 39573425e1a..2ea14b91b1a 100644 --- a/nova/src/main/resources/assets/nova/lang/sv_se.json +++ b/nova/src/main/resources/assets/nova/lang/sv_se.json @@ -161,5 +161,8 @@ "block.nova.unknown.message": "Ingen addon registrerade detta blocktillstånd: ID: %s, Serialiserad: %s", "command.nova.give_clientside_stack.success": "Lade till en klientversion av %s i din inventering.", "command.nova.show_item_behaviors.success": "Denna %s har %s objektbeteende(n):\n%s", - "command.nova.show_item_behaviors.no_item": "Håll ett föremål i din huvudhand och försök igen." + "command.nova.show_item_behaviors.no_item": "Håll ett föremål i din huvudhand och försök igen.", + "command.nova.recalculate_leave_properties.done": "Bearbetade %s löv och gjorde %s löv icke-beständiga.", + "command.nova.show_vanilla_block_state.success": "Du tittar på blocktillståndet %s.", + "command.nova.show_vanilla_block_state.failure": "Titta på ett block och försök igen." } diff --git a/nova/src/main/resources/assets/nova/lang/tr_tr.json b/nova/src/main/resources/assets/nova/lang/tr_tr.json index 04c86e5a53d..5f65a531992 100644 --- a/nova/src/main/resources/assets/nova/lang/tr_tr.json +++ b/nova/src/main/resources/assets/nova/lang/tr_tr.json @@ -161,5 +161,8 @@ "command.nova.reload_configs.failure": "Yapılandırmalar yeniden yüklenemedi, daha fazla bilgi için konsolu kontrol edin.", "command.nova.reload_recipes.failure": "Tarifler yeniden yüklenemedi, daha fazla bilgi için konsolu kontrol edin.", "command.nova.show_item_behaviors.success": "Bu %s, %s öğe davranış(lar)ına sahiptir:\n%s", - "command.nova.show_item_behaviors.no_item": "Lütfen ana elinizde bir eşya tutun ve tekrar deneyin." + "command.nova.show_item_behaviors.no_item": "Lütfen ana elinizde bir eşya tutun ve tekrar deneyin.", + "command.nova.recalculate_leave_properties.done": "s yaprakları işlendi ve %s yaprakları kalıcı olmayan hale getirildi.", + "command.nova.show_vanilla_block_state.success": "Blok durumu %s'ye bakıyorsunuz.", + "command.nova.show_vanilla_block_state.failure": "Lütfen bir bloğa bakın ve tekrar deneyin." } diff --git a/nova/src/main/resources/assets/nova/lang/uk_ua.json b/nova/src/main/resources/assets/nova/lang/uk_ua.json index 475aec86311..86cd87588dd 100644 --- a/nova/src/main/resources/assets/nova/lang/uk_ua.json +++ b/nova/src/main/resources/assets/nova/lang/uk_ua.json @@ -161,5 +161,8 @@ "command.nova.network_debug.nova.energy.on": "Увімкнено перегляд налагодження для Energy-Networks.", "command.nova.show_network_node_info.initialized": "Ініціалізовано мережею: %s", "command.nova.show_item_behaviors.success": "Цей %s має поведінку %s елементів:\n%s", - "command.nova.show_item_behaviors.no_item": "Будь ласка, візьміть предмет в основну руку і спробуйте ще раз." + "command.nova.show_item_behaviors.no_item": "Будь ласка, візьміть предмет в основну руку і спробуйте ще раз.", + "command.nova.recalculate_leave_properties.done": "Обробив листя %s і зробив листя %s нестійким.", + "command.nova.show_vanilla_block_state.success": "Ви дивитеся на стан блоку %s.", + "command.nova.show_vanilla_block_state.failure": "Будь ласка, подивіться на блок і спробуйте ще раз." } diff --git a/nova/src/main/resources/assets/nova/lang/vi_vn.json b/nova/src/main/resources/assets/nova/lang/vi_vn.json index 9e26dfeeb6e..6de523c5496 100644 --- a/nova/src/main/resources/assets/nova/lang/vi_vn.json +++ b/nova/src/main/resources/assets/nova/lang/vi_vn.json @@ -1 +1,163 @@ -{} \ No newline at end of file +{ + "menu.nova.side_config.simple_mode.unavailable": "Không thể chuyển sang chế độ đơn giản do một số cài đặt nâng cao.", + "command.nova.network_debug.nova.energy.on": "Đã bật chế độ gỡ lỗi cho Mạng lưới Năng lượng.", + "command.nova.list_nearby.success": "Trong số %s giá để giáp trong chunk của bạn, %s là thực thể khối (tile entity).", + "command.nova.network_debug.nova.fluid.on": "Đã bật chế độ gỡ lỗi cho Mạng lưới Chất lỏng.", + "command.nova.network_cluster_debug.off": "Đã tắt chế độ gỡ lỗi cho Cụm hệ thống Mạng.", + "command.nova.resource_pack.reupload.start": "Đang tải gói tài nguyên lên máy chủ...", + "command.nova.reload_configs.failure": "Không thể tải lại cấu hình, kiểm tra console để biết thêm thông tin.", + "command.nova.show_block_data.nova_block": "Bạn đang nhìn vào khối Nova không phải thực thể khối %s.", + "command.nova.show_network_node_info.end_point.header": "%s (đầu cuối) này có dữ liệu sau:", + "command.nova.show_block_data.vanilla_tile_entity": "Bạn đang nhìn vào thực thể khối bình thường %s. Nó có dữ liệu Nova sau:\n%s", + "command.nova.show_block_model_data.display_entity.model": "- Đối tượng: %s với dữ liệu: %s\n Dịch chuyển: %s\n Xoay trái: %s\n Tỉ lệ: %s\n Xoay phải: %s", + "command.nova.advanced_tooltips.all.failure": "Tooltip nâng cao cho toàn bộ vật phẩm đã được bật rồi.", + "command.nova.resource_pack.create.success": "Đã hoàn thành tạo gói tài nguyên. Bạn nên khởi động lại máy chủ.", + "command.nova.reload_recipes.failure": "Không thể tải lại các công thức chế tạo, kiểm tra console để biết thêm thông tin.", + "command.nova.remove_invalid_vtes.failure": "Không có vật thể khối không hợp lệ nào được tìm thấy.", + "nova.outdated_version": "Bạn đang chạy một phiên bản cũ: %s.\nHãy cập nhật lên phiên bản mới nhất tại %s.", + "item.cbf_tags": "CBF: %s thẻ", + "menu.nova.color_picker": "Chọn một màu...", + "menu.nova.color_picker.red": "Đỏ: %s", + "menu.nova.color_picker.green": "Xanh Lá: %s", + "command.nova.modeldata.no-nova-item": "Hãy cầm một vật phẩm của Nova trên tay phải của bạn và thử lại.", + "command.nova.fill.success": "Đã lấp đầy thành công vùng từ x: %s, y: %s, z: %s tới x: %s, y: %s, z: %s với %s.", + "command.nova.give_clientside_stack.success": "Đã đưa cho bạn phiên bản phía máy khách của %s vào túi đồ của bạn.", + "command.nova.copy_clientside_stack.success": "Đã thêm một bản sao phiên bản máy khách của %s vào túi đồ của bạn.", + "command.nova.search_block.result": "Tìm thấy %s tại %s.", + "command.nova.search_block.done": "Tìm kiếm hoàn thành.", + "command.nova.recipe.no-recipe": "Vật phẩm này hiện không có công thức chế tạo.", + "command.nova.usage.no-usage": "Vật phẩm này hiện không có công dụng nào cả.", + "command.nova.network_reload.success": "Toàn bộ mạng lưới đang kích hoạt đã được tải lại.", + "command.nova.resource_pack.create.start": "Đang tạo gói tài nguyên...", + "command.nova.invalidate_char_sizes.success": "Đã vô hiệu hóa tất cả kích thước ký tự thành công. Chúng sẽ được tính toán lại trong quá trình tạo gói tài nguyên tiếp theo.", + "nova.tile_entity_limits.amount_per_world_total.deny": "Bạn không thể đặt thêm thực thể khối trong thế giới này nữa.", + "nova.tile_entity_limits.amount_per_chunk_total.deny": "Bạn không thể đặt thêm thực thể khối trong chunk này nữa.", + "menu.nova.side_config.up": "Bên trên", + "menu.nova.side_config.down": "Bên dưới", + "menu.nova.side_config.none": "Trống", + "menu.nova.side_config.output": "Đầu ra", + "menu.nova.side_config.input": "Đầu vào", + "menu.nova.side_config.input_output": "Đầu vào & Đầu ra", + "menu.nova.side_config.simple_mode": "Chế độ Đơn giản", + "menu.nova.color_picker.blue": "Xanh Lam: %s", + "menu.nova.color_picker.current_color": "Màu Hiện Tại", + "menu.nova.region.size": "Kích Cỡ Vùng: %s", + "menu.nova.region.increase": "Mở rộng Khu vực", + "menu.nova.region.decrease": "Thu hẹp Khu vực", + "inventory.nova.default": "Túi đồ", + "inventory.nova.all": "Toàn bộ Túi đồ", + "inventory.nova.input": "Túi đồ vào", + "inventory.nova.output": "Túi đồ ra", + "container.nova.fluid_tank": "Bình Chứa Chất Lỏng", + "container.nova.lava_tank": "Bồn Chứa Dung Nham", + "container.nova.water_tank": "Bồn Chứa Nước", + "command.nova.addons.header": "Các tiện ích mở rộng (%s): ", + "command.nova.no-players": "Không tìm thấy người chơi nào cả.", + "command.nova.no-item-in-hand": "Hãy cầm một vật phẩm trên tay của bạn và thử lại.", + "command.nova.give.success": "Đã trao %s %s cho %s", + "command.nova.get_nearest_data.failed": "Không tìm thấy một thực thể khối ở gần bạn.", + "command.nova.remove_obsolete_models.success": "Đã loại bỏ %s giá để giáp.", + "command.nova.remove_tile_entities.success": "Đã loại bỏ %s thực thể khối.", + "command.nova.modeldata.block": "Khối %s sử dụng %s và có dữ liệu sau: %s.", + "nova.tile_entity_limits.amount_global.deny": "Bạn không thể đặt thêm thực thể khối thuộc loại này nữa.", + "nova.tile_entity_limits.amount_per_world.deny": "Bạn không thể đặt thêm thực thể khối thuộc loại này trong thế giới này nữa.", + "waila.nova.required_tool": "Công cụ: ", + "waila.nova.required_tool.none": "Không cần công cụ", + "waila.nova.required_tool.unbreakable": "Không thể phá vỡ", + "nova.tile_entity_limits.type_blacklist.deny": "Bạn không được phép đặt bất kỳ thực thể khối thuộc loại này.", + "nova.tile_entity_limits.world_blacklist.deny": "Bạn không được phép đặt bất kỳ thực thể khối thuộc Nova trong thế giới này.", + "nova.tile_entity_limits.type_world_blacklist.deny": "Bạn không được phép đặt bất kỳ thực thể khối trong thế giới này.", + "command.nova.resource_pack.reupload.fail": "Không thể tải gói tài nguyên lên máy chủ!", + "command.nova.resource_pack.reupload.success": "Gói tài nguyên đã được tải lên %s.", + "command.nova.reload_configs.start": "Đang tải lại cấu hình...", + "command.nova.reload_configs.success": "Đã tải lại thành công %s file cấu hình: %s.", + "command.nova.reload_configs.none": "Không có cấu hình nào bị thay đổi.", + "command.nova.reload_recipes.start": "Đang tải lại các công thức chế tạo...", + "command.nova.reload_recipes.success": "Đã tải lại các công thức chế tạo.", + "command.nova.advanced_tooltips.off.success": "Đã tắt tooltip nâng cao.", + "command.nova.advanced_tooltips.off.failure": "Tooltip nâng cao đã được tắt rồi.", + "command.nova.advanced_tooltips.nova.success": "Đã bật tooltip nâng cao cho các vật phẩm của Nova.", + "command.nova.advanced_tooltips.nova.failure": "Tooltip nâng cao cho các vật phẩm Nova đã được bật rồi.", + "command.nova.advanced_tooltips.all.success": "Đã bật tooltip nâng cao cho toàn bộ vật phẩm.", + "command.nova.waila.off": "Đã tắt WAILA.", + "command.nova.waila.already_off": "WAILA đã được tắt rồi.", + "command.nova.waila.on": "Đã bật WAILA.", + "command.nova.waila.already_on": "WAILA đã được bật rồi.", + "command.nova.remove_invalid_vtes.success": "Đã loại bỏ thành công %s vật thể khối không hợp lệ.", + "command.nova.list_blocks.success": "Tổng cộng: %s", + "nova.tile_entity_limits.amount_per_chunk.deny": "Bạn không thể đặt thêm thực thể khối thuộc loại này trong chunk này nữa.", + "nova.tile_entity_limits.amount_global_total.deny": "Bạn không thể đặt thêm thực thể khối nào nữa.", + "item.nova.unknown_item_filter": "Bộ lọc Item không khả dụng", + "item.nova.unknown_item_filter.description": "Tiện ích mở rộng của bộ lọc item này hiện không khả dụng hoặc chưa được đăng kí.", + "block.nova.unknown": "Khối Không Rõ", + "block.nova.unknown.message": "Không có tiện ích nào đăng kí trạng thái khối: ID: %s, Dữ liệu: %s", + "enchantment.nova.unknown": "Phù phép không tồn tại: %s", + "menu.nova.energy_per_tick": "%s / tick", + "menu.nova.items": "Các vật phẩm", + "menu.nova.items.search": "Tìm một vật phẩm", + "menu.nova.items.search.back": "Quay trở lại túi đồ chính", + "menu.nova.items.search.clear": "Xóa tìm kiếm", + "menu.nova.items.search-item": "Tìm kiếm...", + "menu.nova.items.category.misc": "Khác", + "menu.nova.items.cheat_mode.on": "Gian Lận: Bật", + "menu.nova.items.cheat_mode.off": "Gian Lận: Tắt", + "menu.nova.recipe": "Hướng dẫn Chế tạo", + "menu.nova.recipe.back": "Quay lại", + "menu.nova.recipe.item_info": "Thông tin Vật phẩm", + "menu.nova.recipe.time": "Thời gian: %ss", + "menu.nova.side_config": "Cấu hình khác", + "menu.nova.side_config.energy": "Năng Lượng", + "menu.nova.side_config.items": "Vật Phẩm", + "menu.nova.side_config.fluids": "Chất Lỏng", + "menu.nova.side_config.front": "Phía trước", + "menu.nova.side_config.left": "Bên trái", + "menu.nova.side_config.back": "Phía sau", + "menu.nova.side_config.right": "Bên phải", + "menu.nova.side_config.top": "Phía trên", + "menu.nova.side_config.bottom": "Phía dưới", + "menu.nova.side_config.north": "Phía bắc", + "menu.nova.side_config.east": "Phía đông", + "menu.nova.side_config.south": "Phía nam", + "menu.nova.side_config.west": "Phía tây", + "menu.nova.side_config.advanced_mode": "Chế độ Nâng cao", + "menu.nova.paged.back": "Trang trước", + "menu.nova.paged.forward": "Trang tiếp", + "menu.nova.paged.go_infinite": "Đi tới trang %s", + "menu.nova.paged.go": "Đi tới trang %s/%s", + "menu.nova.paged.limit_min": "Bạn không thể quay lại thêm nữa", + "menu.nova.paged.limit_max": "Không còn trang nào nữa cả", + "menu.nova.visual_region.hide": "Ẩn khu vực", + "menu.nova.visual_region.show": "Hiện khu vực", + "command.nova.network_debug.nova.energy.off": "Đã tắt chế độ gỡ lỗi cho Mạng lưới Năng lượng.", + "command.nova.network_debug.nova.item.on": "Đã bật chế độ gỡ lỗi cho Mạng lưới Vật phẩm.", + "command.nova.network_debug.nova.item.off": "Đã tắt chế độ gỡ lỗi cho Mạng lưới Vật phẩm.", + "command.nova.network_debug.nova.fluid.off": "Đã tắt chế độ gỡ lỗi cho Mạng lưới Chất lỏng.", + "command.nova.network_cluster_debug.on": "Đã bật chế độ gỡ lỗi cho Cụm hệ thống Mạng.", + "command.nova.reregister_network_nodes.success": "Đã đăng ký lại thành công %s nút mạng.", + "command.nova.hitbox_debug": "Đã bật/tắt chế độ xem gỡ lỗi cho các hộp hitbox ảo.", + "command.nova.render_distance": "Khoảng cách hiển thị của bạn đã được đặt thành %s.", + "command.nova.show_block_data.nova_tile_entity": "Bạn đang nhìn vào thực thể ô Nova %s. Nó có dữ liệu sau:\n%s", + "command.nova.show_block_data.vanilla_block": "Bạn đang nhìn vào khối bình thường %s.", + "command.nova.show_block_data.failure": "Hãy nhìn vào một khối vào thử lại.", + "command.nova.show_block_model_data.model_less": "Khối %s sử dụng khối mặc định sau: %s.", + "command.nova.show_block_model_data.backing_state": "%s này sử dụng cấu hình trạng thái nền sau:\nKhối: %s\nBiến thể: %s", + "command.nova.show_block_model_data.display_entity": "%s này sử dụng cấu hình thực thể hiển thị sau:\nLoại hitbox: %s\nCác mô hình (%s):\n%s", + "command.nova.show_block_model_data.failure": "Hãy nhìn vào một khối Nova và thử lại.", + "command.nova.show_network_node_info.end_point.networks.entry": "Loại: %s, Hướng: %s, ID: %s", + "command.nova.show_network_node_info.bridge.header": "%s (cầu nối mạng) này có dữ liệu sau:", + "command.nova.show_network_node_info.bridge.supported_network_types": "Các loại mạng được hỗ trợ: %s", + "command.nova.show_network_node_info.bridge.allowed_faces": "Các hướng có thể sử dụng: %s", + "command.nova.show_network_node_info.bridge.networks.entry": "Loại: %s, ID: %s", + "command.nova.show_network_node_info.initialized": "Đã khởi tạo mạng lưới: %s", + "command.nova.show_network_node_info.networks.header": "Các mạng lưới (%s):", + "command.nova.show_network_node_info.connected_nodes.header": "Các kết nối (%s):", + "command.nova.show_network_node_info.connected_nodes.entry": "Loại: %s, Hướng: %s, Nút: %s", + "command.nova.show_network_node_info.linked_nodes.header": "Các liên kết (%s):", + "command.nova.show_network_node_info.linked_nodes.entry": "Nút: %s", + "command.nova.show_network_node_info.network": "Loại: %s\nID: %s\nCác nút: %s (%s cầu nối, %s điểm cuối)", + "command.nova.show_network_node_info.node": "Tên: %s\nVị trí: %s, x: %s, y: %s, z: %s", + "command.nova.show_network_node_info.failure": "Khối tại vị trí: %s, x: %s, y: %s, z: %s không phải là một nút mạng.", + "command.nova.show_item_model_data.success": "%s này với mô hình %s sử dụng loại vật phẩm %s với dữ liệu %s.", + "command.nova.show_item_model_data.no_item": "Hãy cầm một vật phẩm Nova trên tay phải của bạn và thử lại.", + "command.nova.modeldata.item": "Vật phẩm %s sử dụng %s và có dữ liệu sau: %s." +} diff --git a/nova/src/main/resources/assets/nova/lang/zh_cn.json b/nova/src/main/resources/assets/nova/lang/zh_cn.json index e8e6dcbd456..8b8bc1a1adb 100644 --- a/nova/src/main/resources/assets/nova/lang/zh_cn.json +++ b/nova/src/main/resources/assets/nova/lang/zh_cn.json @@ -161,5 +161,8 @@ "block.nova.unknown.message": "没有插件注册此区块状态:ID: %s, 序列化: %s", "command.nova.show_network_node_info.connected_nodes.header": "连接 (%s):", "command.nova.show_item_behaviors.success": "该 %s 具有 %s 项行为:\n%s", - "command.nova.show_item_behaviors.no_item": "请用您的主手拿着一件物品再试一次。" + "command.nova.show_item_behaviors.no_item": "请用您的主手拿着一件物品再试一次。", + "command.nova.recalculate_leave_properties.done": "处理了 %s 片树叶,并使 %s 片树叶成为非持久性树叶。", + "command.nova.show_vanilla_block_state.success": "您正在查看的是块状态 %s。", + "command.nova.show_vanilla_block_state.failure": "请查看一个区块,然后再试一次。" } diff --git a/nova/src/main/resources/assets/nova/lang/zh_hk.json b/nova/src/main/resources/assets/nova/lang/zh_hk.json index d09e457f55c..c90150539f6 100644 --- a/nova/src/main/resources/assets/nova/lang/zh_hk.json +++ b/nova/src/main/resources/assets/nova/lang/zh_hk.json @@ -161,5 +161,8 @@ "block.nova.unknown": "未知块", "command.nova.reload_configs.none": "没有更改配置。", "command.nova.show_item_behaviors.success": "该 %s 具有 %s 项行为:\n%s", - "command.nova.show_item_behaviors.no_item": "请用您的主手拿着一件物品再试一次。" + "command.nova.show_item_behaviors.no_item": "请用您的主手拿着一件物品再试一次。", + "command.nova.recalculate_leave_properties.done": "处理了 %s 片树叶,并使 %s 片树叶成为非持久性树叶。", + "command.nova.show_vanilla_block_state.success": "您正在查看的是块状态 %s。", + "command.nova.show_vanilla_block_state.failure": "请查看一个区块,然后再试一次。" } diff --git a/nova/src/main/resources/assets/nova/models/item/canvas_pixel.json b/nova/src/main/resources/assets/nova/models/item/canvas_pixel.json new file mode 100644 index 00000000000..5eddd896057 --- /dev/null +++ b/nova/src/main/resources/assets/nova/models/item/canvas_pixel.json @@ -0,0 +1,6 @@ +{ + "parent": "nova:item/gui_item", + "textures": { + "0": "nova:item/white" + } +} \ No newline at end of file diff --git a/nova/src/main/resources/assets/nova/textures/font/waila/2/end.png b/nova/src/main/resources/assets/nova/textures/font/waila/2/end.png index c9c6da0a10d..7887211fbf6 100644 Binary files a/nova/src/main/resources/assets/nova/textures/font/waila/2/end.png and b/nova/src/main/resources/assets/nova/textures/font/waila/2/end.png differ diff --git a/nova/src/main/resources/assets/nova/textures/font/waila/2/start.png b/nova/src/main/resources/assets/nova/textures/font/waila/2/start.png index 878573baa38..975154d945f 100644 Binary files a/nova/src/main/resources/assets/nova/textures/font/waila/2/start.png and b/nova/src/main/resources/assets/nova/textures/font/waila/2/start.png differ diff --git a/nova/src/main/resources/assets/nova/textures/font/waila/3/end.png b/nova/src/main/resources/assets/nova/textures/font/waila/3/end.png index 8af45f24cfc..1d710f08e6f 100644 Binary files a/nova/src/main/resources/assets/nova/textures/font/waila/3/end.png and b/nova/src/main/resources/assets/nova/textures/font/waila/3/end.png differ diff --git a/nova/src/main/resources/assets/nova/textures/font/waila/3/start.png b/nova/src/main/resources/assets/nova/textures/font/waila/3/start.png index 876f5a7255a..ab5abc47543 100644 Binary files a/nova/src/main/resources/assets/nova/textures/font/waila/3/start.png and b/nova/src/main/resources/assets/nova/textures/font/waila/3/start.png differ diff --git a/nova/src/main/resources/assets/nova/textures/font/waila/6/start.png b/nova/src/main/resources/assets/nova/textures/font/waila/6/start.png index 4f8a0608d19..81f02481f20 100644 Binary files a/nova/src/main/resources/assets/nova/textures/font/waila/6/start.png and b/nova/src/main/resources/assets/nova/textures/font/waila/6/start.png differ diff --git a/nova/src/main/resources/assets/nova/textures/item/white.png b/nova/src/main/resources/assets/nova/textures/item/white.png new file mode 100644 index 00000000000..356fd0c9284 Binary files /dev/null and b/nova/src/main/resources/assets/nova/textures/item/white.png differ diff --git a/nova/src/main/resources/configs/config.yml b/nova/src/main/resources/configs/config.yml index 2e08d3cd46e..5625968709d 100644 --- a/nova/src/main/resources/configs/config.yml +++ b/nova/src/main/resources/configs/config.yml @@ -43,10 +43,12 @@ resource_pack: # Whether Minecraft assets should be downloaded from GitHub or Mojang's API. # GitHub is faster since it's a single zip file. If you're unable to access GitHub, change this to "mojang". minecraft_assets_source: github - # If Nova should use the uniform font for moved versions of the default font and calculation of char sizes for the default font. - # The uniform font can be activated client-side by toggling "Force Unicode Font" in Minecraft's language settings. - # If the players on your server are expected to have that option enabled, you should set this to true. + # Whether Nova should assume the uniform font for char size calculation. + # If players on your server are expected to "Force Unicode Font" in Minecraft's font settings enabled, you should set this to true. force_uniform_font: false + # Whether Nova should assume japanese glyph variants for char size calculation. + # If the players on your server are expected to have "Japanese Glyph Variants" in Minecraft's font settings enabled, you should set this to true. + japanese_glyph_variants: false # The render distance for fake (packet-based) entities from Nova, in chunks. # Note that the render distance is also limited by the entity render distance of the client. diff --git a/nova/src/main/resources/paper-plugin.yml b/nova/src/main/resources/paper-plugin.yml index 68f0dee1eb1..edb790f55ac 100644 --- a/nova/src/main/resources/paper-plugin.yml +++ b/nova/src/main/resources/paper-plugin.yml @@ -28,7 +28,7 @@ dependencies: required: false ItemsAdder: required: false - Oraxen: + Nexo: required: false MMOItems: required: false diff --git a/nova/src/test/kotlin/xyz/xenondevs/nova/config/ConfigExtractorTest.kt b/nova/src/test/kotlin/xyz/xenondevs/nova/config/ConfigExtractorTest.kt index c0fe1581d2e..da88e83e99a 100644 --- a/nova/src/test/kotlin/xyz/xenondevs/nova/config/ConfigExtractorTest.kt +++ b/nova/src/test/kotlin/xyz/xenondevs/nova/config/ConfigExtractorTest.kt @@ -7,6 +7,7 @@ import xyz.xenondevs.commons.provider.mutableProvider import java.nio.file.Path import kotlin.io.path.copyTo import kotlin.io.path.readText +import kotlin.io.path.writeText import kotlin.test.assertEquals class ConfigExtractorTest { @@ -77,6 +78,19 @@ class ConfigExtractorTest { assertConfigEquals(source("server-config-updated-expected.yml"), dest) } + @Test + fun `test empty server config`() { + val sourceOriginal = source("internal-config-original.yml") + val dest = dest() + + val extractor = ConfigExtractor(mutableProvider(HashMap())) + extractor.extract(CONFIG_ID, sourceOriginal, dest) + dest.writeText("") // user deleted all entries + extractor.extract(CONFIG_ID, sourceOriginal, dest) + + assertConfigEquals(sourceOriginal, dest) + } + private fun source(path: String): Path = Path.of(javaClass.getResource("/configs/$path")!!.toURI()) diff --git a/settings.gradle.kts b/settings.gradle.kts index eef91e040c4..1872940a425 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,12 +6,11 @@ include("nova-api") include("nova-gradle-plugin") // hooks -include("nova-hooks:nova-hook-fastasyncworldedit") include("nova-hooks:nova-hook-griefprevention") include("nova-hooks:nova-hook-itemsadder") include("nova-hooks:nova-hook-luckperms") include("nova-hooks:nova-hook-mmoitems") -include("nova-hooks:nova-hook-oraxen") +include("nova-hooks:nova-hook-nexo") include("nova-hooks:nova-hook-plotsquared") include("nova-hooks:nova-hook-protectionstones") include("nova-hooks:nova-hook-quickshop")