fix(platform): support Purpur 26.3 (#1)
OpenProject #416; verified candidate e33d19b94e.
This commit was merged in pull request #1.
This commit is contained in:
commit
df2925b9f6
8 files changed
+51
-28
No files matched your search
@@ -1,12 +1,12 @@
|
||||
# Purpur Tree Feller
|
||||
|
||||
Tree Feller is a Purpur 26.2 plugin that lets Survival players earn animated automatic felling independently for each vanilla tree family.
|
||||
Tree Feller is a Purpur 26.3 plugin that lets Survival players earn animated automatic felling independently for each vanilla tree family.
|
||||
|
||||
The approved behavior and implementation record are in the [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-tree-feller/index.md).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Purpur 26.2 build 2618
|
||||
- Purpur 26.3 build 2651
|
||||
- Java 25
|
||||
|
||||
Repository, checkout and new artifacts use `purpur-tree-feller`. Runtime `TreeFeller`, Java packages/entrypoint, `plugins/TreeFeller/`, commands/permissions and player progression/preferences remain unchanged. Replace the old JAR rather than installing both distributions. Old release tags/assets remain available; new releases no longer support Java 17.
|
||||
@@ -17,11 +17,11 @@ Repository, checkout and new artifacts use `purpur-tree-feller`. Runtime `TreeFe
|
||||
./gradlew clean check jar
|
||||
```
|
||||
|
||||
The plugin JAR is written to `build/libs/purpur-tree-feller-<version>.jar`. `check` includes `nativeTreeTest`, loading real item registries/services from SHA-256-verified Purpur 2618 under `build/tree-runtime/` without starting a listening server. Existing item/felling/undo assertions are retained. `LocalTestHarnessTest` checks parsed Compose settings and runs the actual startup script with isolated command doubles; it does not start Docker.
|
||||
The plugin JAR is written to `build/libs/purpur-tree-feller-<version>.jar`. `check` includes `nativeTreeTest`, loading real item registries/services from SHA-256-verified Purpur 26.3 build 2651 under `build/tree-runtime-26.3-2651/` without starting a listening server. The versioned runtime directory prevents verification from reusing extracted 26.2 state. Existing item/felling/undo assertions are retained. `LocalTestHarnessTest` checks parsed Compose settings and runs the actual startup script with isolated command doubles; it does not start Docker.
|
||||
|
||||
## Local Docker test server
|
||||
|
||||
Build the current plugin and start it on an isolated Java 25/Purpur 26.2 build 2618 server:
|
||||
Build the current plugin and start it on an isolated Java 25/Purpur 26.3 build 2651 server:
|
||||
|
||||
```bash
|
||||
./scripts/start-test-server.sh
|
||||
@@ -95,7 +95,7 @@ Player preferences, administrative locks, progress, unlocks, and known names are
|
||||
|
||||
Tree detection searches matching natural trunk blocks laterally and upward, never below the initiating chop, and requires corresponding foliage or caps. Search bounds prevent unbounded traversal. Leaves, caps, roots, vines, and decorations are not automatically broken.
|
||||
|
||||
Automatic felling supports Purpur 26.2 wooden, stone, copper, iron, golden, diamond, and netherite axes. Additional trunk blocks use the server's `Player.breakBlock` path so block-break cancellation, drops, experience, enchantments, and axe durability remain authoritative. Protection plugins should cancel `BlockBreakEvent` normally. A cancelled additional break stops the remaining felling.
|
||||
Automatic felling supports Purpur 26.3 wooden, stone, copper, iron, golden, diamond, and netherite axes. Additional trunk blocks use the server's `Player.breakBlock` path so block-break cancellation, drops, experience, enchantments, and axe durability remain authoritative. Protection plugins should cancel `BlockBreakEvent` normally. A cancelled additional break stops the remaining felling.
|
||||
|
||||
## Releases
|
||||
|
||||
|
||||
+8
-8
@@ -26,9 +26,9 @@ tasks.withType<JavaCompile>().configureEach {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable")
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.mockito:mockito-core:5.18.0")
|
||||
@@ -44,20 +44,20 @@ tasks.test {
|
||||
}
|
||||
|
||||
// Real item/felling/undo regressions require the declared runtime's registries and service providers.
|
||||
val treeRuntime = layout.buildDirectory.dir("tree-runtime")
|
||||
val treeRuntime = layout.buildDirectory.dir("tree-runtime-26.3-2651")
|
||||
val downloadTreeRuntime = tasks.register("downloadTreeRuntime") {
|
||||
val launcher = treeRuntime.map { it.file("purpur-26.2-2618.jar") }
|
||||
val launcher = treeRuntime.map { it.file("purpur-26.3-2651.jar") }
|
||||
outputs.file(launcher)
|
||||
doLast {
|
||||
val file = launcher.get().asFile
|
||||
file.parentFile.mkdirs()
|
||||
val connection = URI("https://api.purpurmc.org/v2/purpur/26.2/2618/download").toURL().openConnection()
|
||||
val connection = URI("https://api.purpurmc.org/v2/purpur/26.3/2651/download").toURL().openConnection()
|
||||
connection.connectTimeout = 30_000
|
||||
connection.readTimeout = 120_000
|
||||
val bytes = connection.getInputStream().use { it.readBytes() }
|
||||
val digest = MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) }
|
||||
check(digest == "4a32d046a118804d89ca74ba89b798c98f6d8d1f310c18077ac573597049de31") {
|
||||
"Purpur 2618 checksum mismatch"
|
||||
check(digest == "0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598") {
|
||||
"Purpur 2651 checksum mismatch"
|
||||
}
|
||||
file.writeBytes(bytes)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ val downloadTreeRuntime = tasks.register("downloadTreeRuntime") {
|
||||
val prepareTreeRuntime = tasks.register<JavaExec>("prepareTreeRuntime") {
|
||||
dependsOn(downloadTreeRuntime)
|
||||
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
classpath = files(treeRuntime.map { it.file("purpur-26.2-2618.jar") })
|
||||
classpath = files(treeRuntime.map { it.file("purpur-26.3-2651.jar") })
|
||||
mainClass = "io.papermc.paperclip.Main"
|
||||
jvmArgs("-Dpaperclip.patchonly=true")
|
||||
workingDir(treeRuntime)
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ services:
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
TYPE: PURPUR
|
||||
VERSION: "26.2"
|
||||
PURPUR_BUILD: "2618"
|
||||
VERSION: "26.3"
|
||||
PURPUR_BUILD: "2651"
|
||||
MEMORY: 2G
|
||||
ONLINE_MODE: "false"
|
||||
OPS_FILE: /config/tree-feller-ops.json
|
||||
|
||||
@@ -32,7 +32,7 @@ if [[ -z "$container_id" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting up to ${wait_seconds}s for Purpur 26.2 build 2618..."
|
||||
echo "Waiting up to ${wait_seconds}s for Purpur 26.3 build 2651..."
|
||||
deadline=$((SECONDS + wait_seconds))
|
||||
while (( SECONDS < deadline )); do
|
||||
container_state="$(docker inspect --format '{{.State.Status}}' "$container_id")"
|
||||
@@ -51,7 +51,7 @@ done
|
||||
health_state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
|
||||
if [[ "$health_state" != "healthy" ]]; then
|
||||
docker compose -f "$compose_file" logs --tail=150 minecraft >&2
|
||||
echo "Timed out waiting for Purpur 26.2 build 2618 (status: $health_state)." >&2
|
||||
echo "Timed out waiting for Purpur 26.3 build 2651 (status: $health_state)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ final class NativeRuntime {
|
||||
try (var resources = new MultiPackResourceManager(PackType.SERVER_DATA, packs.openAllSelected())) {
|
||||
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
|
||||
List<Registry.PendingTags<?>> tags = TagLoader.loadTagsForExistingRegistries(resources, layers.getLayer(RegistryLayer.STATIC));
|
||||
List<HolderLookup.RegistryLookup<?>> lookups = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), tags);
|
||||
RegistryAccess.Frozen worldgen = RegistryDataLoader.load(resources, lookups,
|
||||
RegistryDataLoader.WORLDGEN_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, worldgen);
|
||||
List<HolderLookup.RegistryLookup<?>> lookups = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLD), tags);
|
||||
RegistryAccess.Frozen world = RegistryDataLoader.load(resources, lookups,
|
||||
RegistryDataLoader.WORLD_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.WORLD, world);
|
||||
RegistryAccess.Frozen dimensions = RegistryDataLoader.load(resources,
|
||||
Stream.concat(lookups.stream(), worldgen.listRegistries()).toList(),
|
||||
Stream.concat(lookups.stream(), world.listRegistries()).toList(),
|
||||
RegistryDataLoader.DIMENSION_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.DIMENSIONS, dimensions);
|
||||
Class.forName(org.bukkit.Registry.class.getName());
|
||||
|
||||
@@ -21,8 +21,8 @@ class LocalTestHarnessTest {
|
||||
Map<?, ?> service = (Map<?, ?>) ((Map<?, ?>) compose.get("services")).get("minecraft");
|
||||
Map<?, ?> environment = (Map<?, ?>) service.get("environment");
|
||||
assertEquals("PURPUR", environment.get("TYPE"));
|
||||
assertEquals("2618", environment.get("PURPUR_BUILD"));
|
||||
assertEquals("26.2", environment.get("VERSION"));
|
||||
assertEquals("2651", environment.get("PURPUR_BUILD"));
|
||||
assertEquals("26.3", environment.get("VERSION"));
|
||||
assertEquals("itzg/minecraft-server:java25", service.get("image"));
|
||||
assertEquals("tree-feller-test", compose.get("name"));
|
||||
assertEquals("tree-feller-test", service.get("container_name"));
|
||||
@@ -48,7 +48,7 @@ class LocalTestHarnessTest {
|
||||
void startupVerifiesBuildBeforeRecreateAndRetainsWorldState() throws Exception {
|
||||
Result result = runStartup(false);
|
||||
assertEquals(0, result.exitCode(), result.output());
|
||||
assertTrue(result.output().contains("Purpur 26.2 build 2618"));
|
||||
assertTrue(result.output().contains("Purpur 26.3 build 2651"));
|
||||
assertTrue(result.output().contains("Tree Feller test server is ready"));
|
||||
assertTrue(result.calls().indexOf("gradle clean check jar") < result.calls().indexOf("up -d --force-recreate"));
|
||||
assertTrue(result.calls().contains("rcon-cli plugins"));
|
||||
|
||||
@@ -17,6 +17,7 @@ class PluginMetadataTest {
|
||||
|
||||
assertEquals("TreeFeller", metadata.get("name"));
|
||||
assertEquals("games.dmg.treefeller.TreeFellerPlugin", metadata.get("main"));
|
||||
assertEquals("1.20", metadata.get("api-version"));
|
||||
Map<?, ?> commands = (Map<?, ?>) metadata.get("commands");
|
||||
assertNotNull(commands.get("treefeller"));
|
||||
assertNotNull(commands.get("treefelleradmin"));
|
||||
|
||||
@@ -21,9 +21,10 @@ class PurpurDistributionTest {
|
||||
assertEquals(version, metadata.get("version"));
|
||||
assertEquals("TreeFeller", metadata.get("name"), "Retain the data directory and persistent namespace");
|
||||
assertEquals("games.dmg.treefeller.TreeFellerPlugin", metadata.get("main"));
|
||||
assertEquals("1.20", metadata.get("api-version"), "Retain the deliberate compatibility floor");
|
||||
assertTrue(((Map<?, ?>) metadata.get("permissions")).containsKey("treefeller.admin"));
|
||||
assertFalse(jar.stream().anyMatch(entry -> entry.getName().startsWith("org/bukkit/")
|
||||
|| entry.getName().startsWith("org/purpurmc/") || entry.getName().endsWith("Test.class")));
|
||||
assertFalse(jar.stream().anyMatch(entry -> isForbiddenDistributionEntry(entry.getName())));
|
||||
assertNotNull(jar.getJarEntry("games/dmg/treefeller/TreeFellerPlugin.class"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +42,14 @@ class PurpurDistributionTest {
|
||||
@Test
|
||||
void releaseAndCiAgreeWithThePurpurDistributionNameAndToolchain() throws Exception {
|
||||
String build = Files.readString(Path.of("build.gradle.kts"));
|
||||
assertTrue(build.contains("purpur-api:26.2.build.2618-stable"));
|
||||
assertTrue(build.contains("purpur-api:26.3.build.2651-stable"));
|
||||
assertTrue(build.contains("purpur-26.3-2651.jar"));
|
||||
assertTrue(build.contains("https://api.purpurmc.org/v2/purpur/26.3/2651/download"));
|
||||
assertTrue(build.contains("0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598"));
|
||||
assertTrue(build.contains("tree-runtime-26.3-2651"));
|
||||
assertFalse(build.contains("26.2.build.2618-stable") || build.contains("purpur-26.2-2618.jar")
|
||||
|| build.contains("/purpur/26.2/2618/download")
|
||||
|| build.contains("4a32d046a118804d89ca74ba89b798c98f6d8d1f310c18077ac573597049de31"));
|
||||
assertFalse(build.contains("org.spigotmc:spigot-api"));
|
||||
for (String workflow : new String[] {"ci.yml", "release.yml"}) {
|
||||
String content = Files.readString(Path.of(".gitea/workflows", workflow));
|
||||
@@ -50,4 +58,18 @@ class PurpurDistributionTest {
|
||||
assertFalse(content.contains("build/libs/tree-feller-") || content.contains("name: tree-feller-"), workflow);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isForbiddenDistributionEntry(String name) {
|
||||
return name.startsWith("org/bukkit/")
|
||||
|| name.startsWith("org/purpurmc/")
|
||||
|| name.startsWith("io/papermc/paper/")
|
||||
|| name.startsWith("com/destroystokyo/paper/")
|
||||
|| name.startsWith("net/minecraft/")
|
||||
|| name.startsWith("com/comphenix/protocol/")
|
||||
|| name.startsWith("org/junit/")
|
||||
|| name.startsWith("org/mockito/")
|
||||
|| name.endsWith("Test.class")
|
||||
|| name.contains("Test$")
|
||||
|| name.endsWith("NativeRuntime.class");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user