Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.10.0
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