From 8fcabecf63d3833c0458bcc8406b33f6a5ed8c28 Mon Sep 17 00:00:00 2001 From: darbyjack Date: Tue, 16 Jun 2026 16:15:04 -0500 Subject: [PATCH 1/2] fix(parsing): adjust parsing to support paper dev builds better --- .../placeholderapi/PlaceholderAPIPlugin.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java index 004e122..693111b 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java @@ -60,7 +60,8 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { private static PlaceholderAPIPlugin instance; static { - String version = Bukkit.getServer().getBukkitVersion().split("-")[0]; + String version = normalizeBukkitVersion(Bukkit.getServer().getBukkitVersion()); + String suffix; if (version.chars() .filter(c -> c == '.') @@ -68,7 +69,17 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { suffix = "R1"; version = 'v' + version.replace('.', '_') + '_' + suffix; } else { - int minor = Integer.parseInt(version.split("\\.")[2].charAt(0) + ""); + final String[] versionParts = version.split("\\."); + + int minor = 1; + if (versionParts.length > 2 && !versionParts[2].isEmpty()) { + try { + minor = Integer.parseInt(versionParts[2].charAt(0) + ""); + } catch (final NumberFormatException ignored) { + minor = 1; + } + } + version = 'v' + version.replace('.', '_').replace("_" + minor, "") + '_' + "R" + (minor - 1); } @@ -294,4 +305,15 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { } } + @NotNull + private static String normalizeBukkitVersion(@NotNull final String bukkitVersion) { + String version = bukkitVersion.split("-", 2)[0]; + + final int paperBuildMetadataIndex = version.indexOf(".build."); + if (paperBuildMetadataIndex != -1) { + version = version.substring(0, paperBuildMetadataIndex); + } + + return version; + } } From a41bf76656d5e4ef9c0a8c9fb5ad9cef81c49dbd Mon Sep 17 00:00:00 2001 From: darbyjack Date: Tue, 16 Jun 2026 17:31:03 -0500 Subject: [PATCH 2/2] feat(version): implement version resolver that utilizes server build info from paper --- .../placeholderapi/PlaceholderAPIPlugin.java | 35 +---- .../placeholderapi/ServerVersionResolver.java | 133 ++++++++++++++++++ .../PaperServerBuildInfoProvider.java | 42 ++++++ .../ServerVersionResolverTest.java | 64 +++++++++ 4 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 src/main/java/me/clip/placeholderapi/ServerVersionResolver.java create mode 100644 src/paper/java/me/clip/placeholderapi/PaperServerBuildInfoProvider.java create mode 100644 src/test/java/me/clip/placeholderapi/ServerVersionResolverTest.java diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java index 693111b..dbd8e9f 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java @@ -60,28 +60,8 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { private static PlaceholderAPIPlugin instance; static { - String version = normalizeBukkitVersion(Bukkit.getServer().getBukkitVersion()); - - String suffix; - if (version.chars() - .filter(c -> c == '.') - .count() == 1) { - suffix = "R1"; - version = 'v' + version.replace('.', '_') + '_' + suffix; - } else { - final String[] versionParts = version.split("\\."); - - int minor = 1; - if (versionParts.length > 2 && !versionParts[2].isEmpty()) { - try { - minor = Integer.parseInt(versionParts[2].charAt(0) + ""); - } catch (final NumberFormatException ignored) { - minor = 1; - } - } - - version = 'v' + version.replace('.', '_').replace("_" + minor, "") + '_' + "R" + (minor - 1); - } + final String version = ServerVersionResolver.resolve( + Bukkit.getServer().getBukkitVersion()).getLegacyVersion(); boolean isSpigot; try { @@ -305,15 +285,4 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { } } - @NotNull - private static String normalizeBukkitVersion(@NotNull final String bukkitVersion) { - String version = bukkitVersion.split("-", 2)[0]; - - final int paperBuildMetadataIndex = version.indexOf(".build."); - if (paperBuildMetadataIndex != -1) { - version = version.substring(0, paperBuildMetadataIndex); - } - - return version; - } } diff --git a/src/main/java/me/clip/placeholderapi/ServerVersionResolver.java b/src/main/java/me/clip/placeholderapi/ServerVersionResolver.java new file mode 100644 index 0000000..c0e8112 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/ServerVersionResolver.java @@ -0,0 +1,133 @@ +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.clip.placeholderapi; + +import java.util.OptionalInt; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ServerVersionResolver { + + private static final String PAPER_BUILD_INFO_PROVIDER = + "me.clip.placeholderapi.PaperServerBuildInfoProvider"; + + private ServerVersionResolver() { + } + + @NotNull + static ServerBuild resolve(@NotNull final String bukkitVersion) { + return resolve(bukkitVersion, resolvePaperBuildInfo()); + } + + @NotNull + static ServerBuild resolve(@NotNull final String bukkitVersion, + @Nullable final ServerBuild paperBuildInfo) { + if (paperBuildInfo != null) { + return paperBuildInfo; + } + + return new ServerBuild(normalizeBukkitVersion(bukkitVersion), OptionalInt.empty()); + } + + @Nullable + private static ServerBuild resolvePaperBuildInfo() { + try { + final Class provider = Class.forName( + PAPER_BUILD_INFO_PROVIDER, + true, + ServerVersionResolver.class.getClassLoader() + ); + final Object result = provider.getMethod("get").invoke(null); + + if (result instanceof ServerBuild) { + return (ServerBuild) result; + } + } catch (final ReflectiveOperationException | LinkageError ignored) { + // Paper is unavailable or exposes an incompatible ServerBuildInfo API. + } + + return null; + } + + @NotNull + static String normalizeBukkitVersion(@NotNull final String bukkitVersion) { + String version = bukkitVersion.split("-", 2)[0]; + + // Some Paper development versions include build metadata in the Bukkit version. + // Keep this fallback for Paper forks that do not expose ServerBuildInfo. + final int paperBuildMetadataIndex = version.indexOf(".build."); + if (paperBuildMetadataIndex != -1) { + version = version.substring(0, paperBuildMetadataIndex); + } + + return version; + } + + static final class ServerBuild { + + @NotNull + private final String minecraftVersionId; + @NotNull + private final OptionalInt buildNumber; + + ServerBuild(@NotNull final String minecraftVersionId, + @NotNull final OptionalInt buildNumber) { + this.minecraftVersionId = minecraftVersionId; + this.buildNumber = buildNumber; + } + + @NotNull + String getMinecraftVersionId() { + return minecraftVersionId; + } + + @NotNull + OptionalInt getBuildNumber() { + return buildNumber; + } + + @NotNull + String getLegacyVersion() { + String version = minecraftVersionId; + + if (version.chars() + .filter(c -> c == '.') + .count() == 1) { + return 'v' + version.replace('.', '_') + "_R1"; + } + + final String[] versionParts = version.split("\\."); + int minor = 1; + + if (versionParts.length > 2 && !versionParts[2].isEmpty()) { + try { + minor = Integer.parseInt(versionParts[2].charAt(0) + ""); + } catch (final NumberFormatException ignored) { + minor = 1; + } + } + + return 'v' + version.replace('.', '_').replace("_" + minor, "") + + "_R" + (minor - 1); + } + } +} diff --git a/src/paper/java/me/clip/placeholderapi/PaperServerBuildInfoProvider.java b/src/paper/java/me/clip/placeholderapi/PaperServerBuildInfoProvider.java new file mode 100644 index 0000000..1cf4149 --- /dev/null +++ b/src/paper/java/me/clip/placeholderapi/PaperServerBuildInfoProvider.java @@ -0,0 +1,42 @@ +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.clip.placeholderapi; + +import io.papermc.paper.ServerBuildInfo; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class PaperServerBuildInfoProvider { + + private PaperServerBuildInfoProvider() { + } + + @NotNull + public static ServerVersionResolver.ServerBuild get() { + final ServerBuildInfo buildInfo = ServerBuildInfo.buildInfo(); + + return new ServerVersionResolver.ServerBuild( + buildInfo.minecraftVersionId(), + buildInfo.buildNumber() + ); + } +} diff --git a/src/test/java/me/clip/placeholderapi/ServerVersionResolverTest.java b/src/test/java/me/clip/placeholderapi/ServerVersionResolverTest.java new file mode 100644 index 0000000..cf1bcaf --- /dev/null +++ b/src/test/java/me/clip/placeholderapi/ServerVersionResolverTest.java @@ -0,0 +1,64 @@ +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.clip.placeholderapi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.OptionalInt; + +import org.junit.jupiter.api.Test; + +public final class ServerVersionResolverTest { + + @Test + void usesPaperBuildInfoWhenAvailable() { + final ServerVersionResolver.ServerBuild paperBuild = + new ServerVersionResolver.ServerBuild("26.2", OptionalInt.of(10)); + + final ServerVersionResolver.ServerBuild result = + ServerVersionResolver.resolve("26.2.build.10-alpha", paperBuild); + + assertSame(paperBuild, result); + assertEquals("26.2", result.getMinecraftVersionId()); + assertEquals(OptionalInt.of(10), result.getBuildNumber()); + assertEquals("v26_2_R1", result.getLegacyVersion()); + } + + @Test + void stripsPaperBuildMetadataWhenServerBuildInfoIsUnavailable() { + final ServerVersionResolver.ServerBuild result = + ServerVersionResolver.resolve("26.2.build.10-alpha", null); + + assertEquals("26.2", result.getMinecraftVersionId()); + assertEquals(OptionalInt.empty(), result.getBuildNumber()); + assertEquals("v26_2_R1", result.getLegacyVersion()); + } + + @Test + void preservesTraditionalBukkitVersions() { + final ServerVersionResolver.ServerBuild result = + ServerVersionResolver.resolve("1.21.11-R0.1-SNAPSHOT", null); + + assertEquals("1.21.11", result.getMinecraftVersionId()); + assertEquals(OptionalInt.empty(), result.getBuildNumber()); + } +}