Skip to content
Closed
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
6 changes: 3 additions & 3 deletions jitpack.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
before_install:
- sdk install java 21.0.2-open
- sdk use java 21.0.2-open
- sdk install java 25-open
- sdk use java 25-open
- sdk install maven

jdk:
- openjdk21
- openjdk25
16 changes: 9 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
<!-- UTF-8 is our standard encoding for source files -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- Target Java 16 -->
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<!-- Run tests using java 21 -->
<maven.compiler.testSource>21</maven.compiler.testSource>
<maven.compiler.testTarget>21</maven.compiler.testTarget>
<!-- Target Java 25 (required by Paper 26.1.2) -->
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<!-- Run tests using java 25 -->
<maven.compiler.testSource>25</maven.compiler.testSource>
<maven.compiler.testTarget>25</maven.compiler.testTarget>

<!-- Spigot properties -->
<paper.version>1.21.1</paper.version>
<paper.version>26.1.2</paper.version>
<paper.javadocs>https://hub.spigotmc.org/javadocs/spigot/</paper.javadocs>

<!-- Default settings for sonarcloud.io -->
Expand Down Expand Up @@ -221,6 +221,8 @@
<exclude>META-INF/*</exclude>
</excludes>
</filter>
<!-- Exclude dough's MinecraftVersion so our patched version (which handles
the new 26.x year.drop.hotfix format) wins in the shaded jar. -->
</filters>
</configuration>

Expand Down
Binary file added releases/Slimefun-v4.9-UNOFFICIAL-26.1.2.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.github.bakedlibs.dough.versions;

import javax.annotation.Nonnull;

import org.bukkit.Bukkit;
import org.bukkit.Server;

/**
* Patched override of the shaded dough {@code MinecraftVersion}.
*
* The original class calls {@code SemanticVersion.parse()} on the first
* dash-delimited component of {@code Server.getBukkitVersion()}. For
* Minecraft 26.1.2 that component is {@code "26.1.2.build.12"}, which
* contains a non-numeric fourth segment and therefore throws
* {@link IllegalArgumentException} inside {@code SemanticVersion.parse()},
* ultimately crashing the static initialiser of {@code ItemUtils}.
*
* This replacement normalises the string to {@code "major.minor.patch"}
* before parsing so {@code "26.1.2.build.12"} becomes {@code "26.1.2"}.
*/
public class MinecraftVersion extends SemanticVersion {

public MinecraftVersion(int major, int minor, int patch) {
super(major, minor, patch);
}

private MinecraftVersion(@Nonnull SemanticVersion version) {
this(version.getMajorVersion(), version.getMinorVersion(), version.getPatchVersion());
}

@Nonnull
public static MinecraftVersion of(@Nonnull Server server) throws UnknownServerVersionException {
if (server == null) {
throw new UnknownServerVersionException("Server should not be null!", null);
}

String rawBukkitVersion = server.getBukkitVersion();

try {
// Take only the part before the first '-' (e.g. "26.1.2.build.12")
String withoutSuffix = rawBukkitVersion.split("-")[0];

// Split into dot-separated components and take only the leading numeric ones.
String[] parts = withoutSuffix.split("\\.");
int major = 1, minor = 0, patch = 0;
if (parts.length >= 1) {
major = Integer.parseInt(parts[0]);
}
// Legacy "1.x.y" format — skip leading "1" and treat parts[1] as major
if (major == 1 && parts.length >= 2) {
minor = Integer.parseInt(parts[1]);
if (parts.length >= 3) {
// parts[2] might be "1" in "1.21.1" or numeric prefix of "2something"
try {
patch = Integer.parseInt(parts[2]);
} catch (NumberFormatException ignored) {
patch = 0;
}
}
return new MinecraftVersion(major, minor, patch);
}

// Modern "year.drop.hotfix[.extra…]" format — parts[0] is year/major
if (parts.length >= 2) {
minor = Integer.parseInt(parts[1]);
}
if (parts.length >= 3) {
try {
patch = Integer.parseInt(parts[2]);
} catch (NumberFormatException ignored) {
patch = 0;
}
}
return new MinecraftVersion(major, minor, patch);
} catch (Exception x) {
throw new UnknownServerVersionException("Could not recognize version string: " + rawBukkitVersion, x);
}
}

@Nonnull
public static MinecraftVersion get() throws UnknownServerVersionException {
return of(Bukkit.getServer());
}

public static boolean isMocked(@Nonnull Server server) {
Class<?> clazz = server.getClass();
while (clazz != null) {
if (clazz.getName().endsWith("mockbukkit.ServerMock")) {
return true;
}
clazz = clazz.getSuperclass();
}
return false;
}

public static boolean isMocked() {
return isMocked(Bukkit.getServer());
}

@Override
public String getAsString() {
return "Minecraft " + super.getAsString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ public enum MinecraftVersion {
*/
MINECRAFT_1_21(21, 0, "1.21.x"),

/**
* Minecraft (Java Edition) 26.1 &mdash; first release under the new
* year.drop.hotfix versioning scheme. Matches 26.1.0 through 26.1.x.
*/
MINECRAFT_26_1(26, 1, "26.1.x"),

/**
* This constant represents an exceptional state in which we were unable
* to identify the Minecraft Version we are using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,11 @@

import io.github.bakedlibs.dough.common.CommonPatterns;
import io.github.bakedlibs.dough.items.ItemMetaSnapshot;
import io.github.bakedlibs.dough.skins.PlayerHead;
import io.github.bakedlibs.dough.skins.PlayerSkin;
import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion;
import io.github.thebusybiscuit.slimefun4.api.exceptions.PrematureCodeException;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
import io.github.thebusybiscuit.slimefun4.utils.HeadTexture;
import io.github.thebusybiscuit.slimefun4.utils.PlayerSkinUtils;
import io.github.thebusybiscuit.slimefun4.utils.compatibility.VersionedItemFlag;

/**
Expand Down Expand Up @@ -284,8 +283,7 @@ public void setAmount(int amount) {
return new ItemStack(Material.PLAYER_HEAD);
}

PlayerSkin skin = PlayerSkin.fromBase64(getTexture(id, texture));
return PlayerHead.getItemStack(skin);
return PlayerSkinUtils.getItemStackFromBase64(getTexture(id, texture));
}

private static @Nonnull String getTexture(@Nonnull String id, @Nonnull String texture) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ class VersionsCommand extends SubCommand {
* This is the Java version we recommend to use.
* Bump as necessary and adjust the warning.
*/
private static final int RECOMMENDED_JAVA_VERSION = 16;
private static final int RECOMMENDED_JAVA_VERSION = 25;

/**
* This is the notice that will be displayed when an
* older version of Java is detected.
*/
private static final String JAVA_VERSION_NOTICE = "As of Minecraft 1.17 Java 16 will be required!";
private static final String JAVA_VERSION_NOTICE = "As of Minecraft 26.1 Java 25 is required!";

@ParametersAreNonnullByDefault
VersionsCommand(Slimefun plugin, SlimefunCommand cmd) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package io.github.thebusybiscuit.slimefun4.core.services.github;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.bukkit.Bukkit;

import io.github.bakedlibs.dough.skins.PlayerSkin;
import io.github.bakedlibs.dough.skins.UUIDLookup;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;

/**
Expand All @@ -33,6 +36,8 @@
class GitHubTask implements Runnable {

private static final int MAX_REQUESTS_PER_MINUTE = 16;
private static final Pattern UUID_PATTERN = Pattern.compile("\"id\"\\s*:\\s*\"([0-9a-fA-F]{32})\"");
private static final Pattern TEXTURE_VALUE_PATTERN = Pattern.compile("\"name\"\\s*:\\s*\"textures\"\\s*,\\s*\"value\"\\s*:\\s*\"([^\"]+)\"");
private final GitHubService gitHubService;

GitHubTask(@Nonnull GitHubService github) {
Expand Down Expand Up @@ -107,9 +112,6 @@ private int requestTexture(@Nonnull Contributor contributor, @Nonnull Map<String
} catch (IllegalArgumentException x) {
// There cannot be a texture found because it is not a valid MC username
contributor.setTexture(null);
} catch (InterruptedException x) {
Slimefun.logger().log(Level.WARNING, "The contributors thread was interrupted!");
Thread.currentThread().interrupt();
} catch (Exception x) {
// Too many requests
Slimefun.logger().log(Level.WARNING, "Attempted to refresh skin cache, got this response: {0}: {1}", new Object[] { x.getClass().getSimpleName(), x.getMessage() });
Expand All @@ -129,25 +131,72 @@ private int requestTexture(@Nonnull Contributor contributor, @Nonnull Map<String
return 0;
}

private @Nullable String pullTexture(@Nonnull Contributor contributor, @Nonnull Map<String, String> skins) throws InterruptedException, ExecutionException, TimeoutException {
private @Nullable String pullTexture(@Nonnull Contributor contributor, @Nonnull Map<String, String> skins) throws IOException {
Optional<UUID> uuid = contributor.getUniqueId();

if (!uuid.isPresent()) {
CompletableFuture<UUID> future = UUIDLookup.getUuidFromUsername(Slimefun.instance(), contributor.getMinecraftName());

// Fixes #3241 - Do not wait for more than 30 seconds
uuid = Optional.ofNullable(future.get(30, TimeUnit.SECONDS));
uuid.ifPresent(contributor::setUniqueId);
UUID resolved = lookupUuid(contributor.getMinecraftName());
if (resolved != null) {
contributor.setUniqueId(resolved);
uuid = Optional.of(resolved);
}
}

if (uuid.isPresent()) {
CompletableFuture<PlayerSkin> future = PlayerSkin.fromPlayerUUID(Slimefun.instance(), uuid.get());
Optional<String> skin = Optional.of(future.get().getProfile().getBase64Texture());
skins.put(contributor.getMinecraftName(), skin.orElse(""));
return skin.orElse(null);
String texture = lookupTexture(uuid.get());
skins.put(contributor.getMinecraftName(), texture == null ? "" : texture);
return texture;
} else {
return null;
}
}

private @Nullable UUID lookupUuid(@Nonnull String username) throws IOException {
String body = fetch("https://api.mojang.com/users/profiles/minecraft/" + username);
if (body == null) {
return null;
}
Matcher m = UUID_PATTERN.matcher(body);
if (!m.find()) {
return null;
}
String raw = m.group(1);
String formatted = raw.substring(0, 8) + '-' + raw.substring(8, 12) + '-'
+ raw.substring(12, 16) + '-' + raw.substring(16, 20) + '-'
+ raw.substring(20);
return UUID.fromString(formatted);
}

private @Nullable String lookupTexture(@Nonnull UUID uuid) throws IOException {
String body = fetch("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid);
if (body == null) {
return null;
}
Matcher m = TEXTURE_VALUE_PATTERN.matcher(body);
return m.find() ? m.group(1) : null;
}

private @Nullable String fetch(@Nonnull String endpoint) throws IOException {
URL url = URI.create(endpoint).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10_000);
conn.setReadTimeout(10_000);
conn.setRequestProperty("User-Agent", "Slimefun4");
int status = conn.getResponseCode();
if (status == 429) {
throw new IOException("429 Too Many Requests");
}
if (status != 200) {
return null;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
import io.github.thebusybiscuit.slimefun4.core.services.LocalizationService;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;

import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;

/**
* This is an abstract parent class of {@link LocalizationService}.
Expand Down Expand Up @@ -362,8 +360,7 @@ public void sendActionbarMessage(@Nonnull Player player, @Nonnull String key, bo
String prefix = addPrefix ? getChatPrefix() : "";
String message = ChatColors.color(prefix + getMessage(player, key));

BaseComponent[] components = TextComponent.fromLegacyText(message);
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, components);
player.sendActionBar(LegacyComponentSerializer.legacySection().deserialize(message));
}

public void sendMessage(@Nonnull CommandSender recipient, @Nonnull String key) {
Expand Down
Loading
Loading