Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ kapt.use.k2=true
org.gradle.jvmargs=-Xmx8G
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
javaVersion=25
mcVersion=26.1.1
mcVersion=26.1.2
group=dev.slne.surf.api
version=3.9.5
version=3.9.6
relocationPrefix=dev.slne.surf.api.libs
snapshot=false
33 changes: 25 additions & 8 deletions surf-api-paper/surf-api-paper-plugin-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import net.minecrell.pluginyml.paper.PaperPluginDescription
import xyz.jpenilla.runpaper.task.RunServer

plugins {
`core-convention`
Expand All @@ -24,6 +25,7 @@ paper {
description = "Test plugin for Surf API for Paper"
author = "twisti"
apiVersion = "1.21"
foliaSupported = true

serverDependencies {
register("CommandAPI") {
Expand All @@ -40,21 +42,36 @@ paper {
}
}

tasks {
runServer {
dependsOn(":surf-api-paper:surf-api-paper-server:shadowJar")
pluginJars.from(project(":surf-api-paper:surf-api-paper-server").tasks.shadowJar)
fun RunServer.configure(folia: Boolean) {
dependsOn(":surf-api-paper:surf-api-paper-server:shadowJar")
pluginJars.from(project(":surf-api-paper:surf-api-paper-server").tasks.shadowJar)

minecraftVersion(findProperty("mcVersion") as String)

minecraftVersion(findProperty("mcVersion") as String)
downloadPlugins {
hangar("CommandAPI", libs.versions.commandapi.get())
modrinth("packetevents", libs.versions.packetevents.plugin.get() + "+spigot")

downloadPlugins {
hangar("CommandAPI", libs.versions.commandapi.get())
modrinth("packetevents", libs.versions.packetevents.plugin.get() + "+spigot")
if (!folia) {
modrinth("luckperms", libs.versions.luckpermsplugin.bukkit.get())
} else {
url("https://ci.lucko.me/job/LuckPerms-Folia/11/artifact/bukkit/loader/build/libs/LuckPerms-Bukkit-5.5.29.jar")
}
}
}

runPaper {
folia.registerTask {
configure(true)
}
}

tasks {
runServer {
configure(false)
}
}

tasks {
shadowJar {
val relocationPrefix: String by project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.slne.surf.api.paper.test.command;

import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.*;
import dev.slne.surf.api.paper.test.command.subcommands.*;
import dev.slne.surf.surfapi.bukkit.test.command.subcommands.*;

Expand Down Expand Up @@ -30,7 +30,8 @@ public SurfApiTestCommand() {
new SurfEventHandlerTest("eventhandler"),
new ShowItemCommand("showitem"),
new SortInvCommand("sortInv"),
new SignedMessageArgumentTest("signedmessage")
new SignedMessageArgumentTest("signedmessage"),
new BlockPdcContainerTest("blockpdc")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package dev.slne.surf.surfapi.bukkit.test.command.subcommands

import com.github.shynixn.mccoroutine.folia.regionDispatcher
import dev.jorel.commandapi.CommandAPICommand
import dev.jorel.commandapi.arguments.LocationType
import dev.jorel.commandapi.kotlindsl.*
import dev.slne.surf.api.paper.command.executors.playerExecutorSuspend
import dev.slne.surf.api.paper.pdc.block.pdc
import dev.slne.surf.api.paper.util.chunkX
import dev.slne.surf.api.paper.util.chunkZ
import dev.slne.surf.api.paper.util.doInChunkAsync
import dev.slne.surf.surfapi.bukkit.test.plugin
import kotlinx.coroutines.withContext
import org.bukkit.Location
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
import org.bukkit.persistence.PersistentDataType
import kotlin.math.abs
import kotlin.math.max

class BlockPdcContainerTest(name: String) : CommandAPICommand(name) {
init {
setCommand()
getCommand()
listCommand()
clearCommand()
copyNearCommand()
copyFarCommand()
}

private fun setCommand() = subcommand("set") {
locationArgument("location", LocationType.BLOCK_POSITION)
textArgument("key")
greedyStringArgument("value")

playerExecutorSuspend { sender, args ->
val location: Location by args
val key: String by args
val value: String by args

val world = location.world ?: run {
sender.sendMessage("Location has no world.")
return@playerExecutorSuspend
}

val nsKey = NamespacedKey(plugin, key)

world.doInChunkAsync(location.chunkX, location.chunkZ) { chunk ->
val block = chunk.getBlock(location.blockX and 15, location.blockY, location.blockZ and 15)
block.pdc().set(nsKey, PersistentDataType.STRING, value)
}

sender.sendMessage("Set '$key' = '$value' on block at (${location.blockX}, ${location.blockY}, ${location.blockZ}).")
}
}

private fun getCommand() = subcommand("get") {
locationArgument("location", LocationType.BLOCK_POSITION)
textArgument("key")

playerExecutorSuspend { sender, args ->
val location: Location by args
val key: String by args

val world = location.world ?: run {
sender.sendMessage("Location has no world.")
return@playerExecutorSuspend
}

val nsKey = NamespacedKey(plugin, key)

val result = world.doInChunkAsync(location.chunkX, location.chunkZ) { chunk ->
val block = chunk.getBlock(location.blockX and 15, location.blockY, location.blockZ and 15)
block.pdc().get(nsKey, PersistentDataType.STRING)
}

if (result != null) {
sender.sendMessage("Block PDC [$key] = '$result' at (${location.blockX}, ${location.blockY}, ${location.blockZ}).")
} else {
sender.sendMessage("No value for key '$key' on block at (${location.blockX}, ${location.blockY}, ${location.blockZ}).")
}
}
}

private fun listCommand() = subcommand("list") {
locationArgument("location", LocationType.BLOCK_POSITION)

playerExecutorSuspend { sender, args ->
val location: Location by args

val world = location.world ?: run {
sender.sendMessage("Location has no world.")
return@playerExecutorSuspend
}

val keys = world.doInChunkAsync(location.chunkX, location.chunkZ) { chunk ->
val block = chunk.getBlock(location.blockX and 15, location.blockY, location.blockZ and 15)
block.pdc().keys.map { it.toString() }
}

if (keys.isEmpty()) {
sender.sendMessage("Block PDC at (${location.blockX}, ${location.blockY}, ${location.blockZ}) is empty.")
} else {
sender.sendMessage("Block PDC keys at (${location.blockX}, ${location.blockY}, ${location.blockZ}) [${keys.size}]:")
keys.forEach { sender.sendMessage(" - $it") }
}
}
}

private fun clearCommand() = subcommand("clear") {
locationArgument("location", LocationType.BLOCK_POSITION)

playerExecutorSuspend { sender, args ->
val location: Location by args

val world = location.world ?: run {
sender.sendMessage("Location has no world.")
return@playerExecutorSuspend
}

world.doInChunkAsync(location.chunkX, location.chunkZ) { chunk ->
val block = chunk.getBlock(location.blockX and 15, location.blockY, location.blockZ and 15)
block.pdc().clear()
}

sender.sendMessage("Cleared block PDC at (${location.blockX}, ${location.blockY}, ${location.blockZ}).")
}
}

private fun copyNearCommand() = subcommand("copy-near") {
locationArgument("source", LocationType.BLOCK_POSITION)
locationArgument("target", LocationType.BLOCK_POSITION)

playerExecutorSuspend { sender, args ->
val source: Location by args
val target: Location by args

if (!isSameWorld(sender, source, target)) {
return@playerExecutorSuspend
}

val chunkDistance = chunkDistance(source, target)
if (chunkDistance > 1) {
sender.sendMessage("copy-near expects source and target in the same region (chunk distance <= 1), got $chunkDistance.")
return@playerExecutorSuspend
}

runCopyTest(sender, source, target, "near")
}
}

private fun copyFarCommand() = subcommand("copy-far") {
locationArgument("source", LocationType.BLOCK_POSITION)
locationArgument("target", LocationType.BLOCK_POSITION)

playerExecutorSuspend { sender, args ->
val source: Location by args
val target: Location by args

if (!isSameWorld(sender, source, target)) {
return@playerExecutorSuspend
}

val chunkDistance = chunkDistance(source, target)
if (chunkDistance < FAR_MIN_CHUNK_DISTANCE) {
sender.sendMessage("copy-far expects blocks to be far apart (chunk distance >= $FAR_MIN_CHUNK_DISTANCE), got $chunkDistance.")
return@playerExecutorSuspend
}

runCopyTest(sender, source, target, "far")
}
}

private fun isSameWorld(sender: Player, source: Location, target: Location): Boolean {
if (source.world == null || target.world == null) {
sender.sendMessage("Both source and target must include a world.")
return false
}

if (source.world != target.world) {
sender.sendMessage("Source and target must be in the same world.")
return false
}

return true
}

private suspend fun runCopyTest(sender: Player, source: Location, target: Location, label: String) {
val value = "$label-copy-${System.currentTimeMillis()}"

val result = runCatching {
val sourcePdc = withContext(plugin.regionDispatcher(source)) {
val sourceBlock = source.block
val sourcePdc = sourceBlock.pdc()
sourcePdc.set(TEST_KEY, PersistentDataType.STRING, value)
sourcePdc
}

withContext(plugin.regionDispatcher(target)) {
val targetBlock = target.block
sourcePdc.copyTo(targetBlock)
targetBlock.pdc().get(TEST_KEY, PersistentDataType.STRING)
}
}

result.onSuccess { copiedValue ->
if (copiedValue == value) {
sender.sendMessage("Block PDC copy test [$label] passed. Value '$copiedValue' was copied successfully.")
} else {
sender.sendMessage("Block PDC copy test [$label] failed. Expected '$value', got '${copiedValue ?: "null"}'.")
}
}.onFailure { error ->
sender.sendMessage("Block PDC copy test [$label] failed with exception: ${error::class.simpleName}: ${error.message}")
}
}

private fun chunkDistance(source: Location, target: Location): Int {
val dx = abs(source.chunkX - target.chunkX)
val dz = abs(source.chunkZ - target.chunkZ)
return max(dx, dz)
}

companion object {
private const val FAR_MIN_CHUNK_DISTANCE = 32
private val TEST_KEY = NamespacedKey(plugin, "block-pdc-copy-test")
}
}

Loading
Loading