完善注释(添加文档注释),添加skinsrestorer皮肤加载支持功能(未经完整测试)

This commit is contained in:
xiaobai
2026-02-14 19:56:16 +08:00
parent b268a74eeb
commit 8c68028924
9 changed files with 540 additions and 59 deletions

View File

@@ -19,6 +19,42 @@ import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 皮肤管理器 - 负责玩家皮肤的获取、缓存和应用
*
* <p>主要功能:
* <ul>
* <li>从多种来源获取玩家皮肤数据SkinsRestorer插件、PlayerProfile、本地缓存</li>
* <li>皮肤数据Base64编码和缓存管理</li>
* <li>自定义模型数据分配和管理</li>
* <li>异步皮肤加载避免阻塞主线程</li>
* <li>皮肤缓存过期清理</li>
* <li>完整的SkinsRestorer插件集成支持</li>
* </ul>
*
* <p>皮肤获取优先级根据配置的source字段
* <ol>
* <li><b>skinsrestorer</b>优先从SkinsRestorer插件获取皮肤纹理数据</li>
* <li><b>player_profile</b>优先使用Bukkit的PlayerProfile API</li>
* <li><b>local_cache</b>:优先从本地缓存加载</li>
* <li>默认Steve皮肤所有来源都失败时的备用</li>
* </ol>
*
* <p><b>SkinsRestorer集成特性</b>
* <ul>
* <li>自动检测SkinsRestorer插件是否安装</li>
* <li>使用反射安全调用SkinsRestorer API避免硬依赖</li>
* <li>获取完整的皮肤纹理数据value和signature</li>
* <li>支持离线服务器避免默认Steve皮肤问题</li>
* <li>优雅降级SkinsRestorer失败时自动回退到其他来源</li>
* </ul>
*
* <p>皮肤缓存默认保留7天过期后自动重新获取。</p>
*
* @author xiaobai
* @version 2.1.0
* @since 1.0.0
*/
public class SkinManager {
private final PlayerBlockLife plugin;
private final Map<UUID, String> playerSkinData = new ConcurrentHashMap<>();
@@ -61,20 +97,40 @@ public class SkinManager {
plugin.logInfo("开始加载皮肤: " + player.getName());
String skinBase64 = null;
String skinSource = plugin.getConfigManager().getSkinSource();
// 检查是否使用SkinsRestorer插件
if (plugin.getConfigManager().useSkinsRestorer()) {
// 根据配置的皮肤来源优先级获取皮肤
if ("skinsrestorer".equalsIgnoreCase(skinSource)) {
// 优先尝试SkinsRestorer
skinBase64 = getSkinFromSkinsRestorer(player);
if (skinBase64 == null) {
plugin.logInfo("SkinsRestorer获取失败尝试PlayerProfile: " + player.getName());
skinBase64 = getSkinFromPlayerProfile(player);
}
} else if ("player_profile".equalsIgnoreCase(skinSource)) {
// 优先尝试PlayerProfile
skinBase64 = getSkinFromPlayerProfile(player);
if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
plugin.logInfo("PlayerProfile获取失败尝试SkinsRestorer: " + player.getName());
skinBase64 = getSkinFromSkinsRestorer(player);
}
} else if ("local_cache".equalsIgnoreCase(skinSource)) {
// 优先从本地缓存加载
if (loadSkinFromCache(playerId)) {
plugin.logInfo("从缓存加载皮肤: " + player.getName());
return;
}
// 缓存不存在,尝试其他来源
skinBase64 = getSkinFromPlayerProfile(player);
if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
skinBase64 = getSkinFromSkinsRestorer(player);
}
}
// 如果SkinsRestorer未启用或获取失败使用PlayerProfile
if (skinBase64 == null) {
skinBase64 = getSkinFromPlayerProfile(player);
}
// 如果所有来源都失败使用默认Steve皮肤
if (skinBase64 == null) {
skinBase64 = getDefaultSteveSkin();
plugin.logWarning("使用默认皮肤: " + player.getName());
plugin.logWarning("所有皮肤来源都失败使用默认Steve皮肤: " + player.getName());
}
if (skinBase64 != null) {
@@ -97,6 +153,37 @@ public class SkinManager {
});
}
/**
* 从SkinsRestorer插件获取玩家皮肤纹理数据
*
* <p>SkinsRestorer是一个流行的皮肤管理插件可以在离线服务器上提供皮肤支持。</p>
*
* <p>此方法使用反射安全调用SkinsRestorer API避免硬依赖。支持离线服务器获取玩家自定义皮肤。</p>
*
* <p>获取流程:
* <ol>
* <li>检查SkinsRestorer插件是否安装</li>
* <li>使用反射获取SkinsRestorer API实例</li>
* <li>优先通过UUID获取皮肤数据更可靠</li>
* <li>如果UUID获取失败回退到使用玩家名获取</li>
* <li>提取皮肤纹理的value和signature字段</li>
* <li>构建完整的Base64编码纹理JSON</li>
* </ol>
* </p>
*
* <p><b>离线服务器优势:</b>
* <ul>
* <li>即使玩家离线也能获取其预设皮肤</li>
* <li>避免总是显示默认Steve皮肤的问题</li>
* <li>支持管理员设置的皮肤和玩家自定义皮肤</li>
* </ul>
* </p>
*
* @param player 要获取皮肤的玩家对象
* @return 完整的Base64编码皮肤纹理JSON如果获取失败返回null
* @throws ClassNotFoundException 如果SkinsRestorer API类未找到插件未安装
* @throws Exception 反射调用过程中的其他异常
*/
private String getSkinFromSkinsRestorer(Player player) {
try {
// 检查SkinsRestorer插件是否存在
@@ -111,20 +198,44 @@ public class SkinManager {
Class<?> skinsRestorerClass = Class.forName("net.skinsrestorer.api.SkinsRestorerAPI");
Object skinsRestorerAPI = skinsRestorerClass.getMethod("getApi").invoke(null);
// 获取玩家皮肤数据
Class<?> skinDataClass = Class.forName("net.skinsrestorer.api.property.SkinProperty");
Object skinProperty = skinsRestorerAPI.getClass().getMethod("getSkinData", String.class)
.invoke(skinsRestorerAPI, player.getName());
// 获取玩家皮肤数据 - 使用UUID而不是玩家名更可靠
Class<?> skinPropertyClass = Class.forName("net.skinsrestorer.api.property.SkinProperty");
Object skinProperty = skinsRestorerAPI.getClass().getMethod("getSkinData", UUID.class)
.invoke(skinsRestorerAPI, player.getUniqueId());
// 如果通过UUID获取失败尝试使用玩家名
if (skinProperty == null) {
skinProperty = skinsRestorerAPI.getClass().getMethod("getSkinData", String.class)
.invoke(skinsRestorerAPI, player.getName());
}
if (skinProperty != null) {
String value = (String) skinProperty.getClass().getMethod("getValue").invoke(skinProperty);
String signature = (String) skinProperty.getClass().getMethod("getSignature").invoke(skinProperty);
String value = (String) skinPropertyClass.getMethod("getValue").invoke(skinProperty);
String signature = (String) skinPropertyClass.getMethod("getSignature").invoke(skinProperty);
// 创建完整的纹理JSON对象
JsonObject textureJson = new JsonObject();
JsonObject texturesJson = new JsonObject();
JsonObject skinJson = new JsonObject();
skinJson.addProperty("url", "http://textures.minecraft.net/texture/" + value);
texturesJson.add("SKIN", skinJson);
textureJson.add("textures", texturesJson);
// 添加时间戳确保唯一性
textureJson.addProperty("timestamp", System.currentTimeMillis());
String base64Texture = java.util.Base64.getEncoder().encodeToString(textureJson.toString().getBytes());
plugin.logInfo("成功从SkinsRestorer获取皮肤: " + player.getName());
return player.getUniqueId().toString();
return base64Texture;
} else {
plugin.logInfo("SkinsRestorer中没有找到玩家 " + player.getName() + " 的皮肤数据");
}
} catch (ClassNotFoundException e) {
plugin.logWarning("SkinsRestorer API类未找到插件可能未安装: " + e.getMessage());
plugin.logWarning("SkinsRestorer API类未找到插件可能未安装或版本不兼容: " + e.getMessage());
} catch (NoSuchMethodException e) {
plugin.logWarning("SkinsRestorer API方法未找到可能是版本不兼容: " + e.getMessage());
} catch (Exception e) {
plugin.logWarning("从SkinsRestorer获取皮肤失败: " + e.getMessage());
}
@@ -132,6 +243,14 @@ public class SkinManager {
return null;
}
/**
* 从PlayerProfile获取玩家皮肤数据
*
* <p>使用Bukkit的PlayerProfile API获取在线玩家的皮肤URL然后转换为Base64纹理。</p>
*
* @param player 要获取皮肤的玩家
* @return 皮肤的Base64纹理值如果获取失败返回null
*/
private String getSkinFromPlayerProfile(Player player) {
try {
PlayerProfile profile = player.getPlayerProfile();
@@ -139,30 +258,27 @@ public class SkinManager {
URL skinUrl = textures.getSkin();
if (skinUrl != null) {
BufferedImage skinImage = ImageIO.read(skinUrl);
if (skinImage == null) {
return null;
}
int headX = 8;
int headY = 8;
int headWidth = 8;
int headHeight = 8;
if (skinImage.getWidth() >= headX + headWidth &&
skinImage.getHeight() >= headY + headHeight) {
BufferedImage headImage = skinImage.getSubimage(headX, headY, headWidth, headHeight);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(headImage, "PNG", baos);
byte[] imageBytes = baos.toByteArray();
JsonObject textureJson = new JsonObject();
JsonObject timestampJson = new JsonObject();
return player.getUniqueId().toString();
}
// 创建纹理JSON对象
JsonObject textureJson = new JsonObject();
JsonObject texturesJson = new JsonObject();
JsonObject skinJson = new JsonObject();
// 直接使用皮肤URL不需要处理图像
skinJson.addProperty("url", skinUrl.toString());
texturesJson.add("SKIN", skinJson);
textureJson.add("textures", texturesJson);
// 添加时间戳确保唯一性
textureJson.addProperty("timestamp", System.currentTimeMillis());
textureJson.addProperty("profileId", player.getUniqueId().toString());
textureJson.addProperty("profileName", player.getName());
String base64Texture = java.util.Base64.getEncoder().encodeToString(textureJson.toString().getBytes());
plugin.logInfo("成功从PlayerProfile获取皮肤: " + player.getName());
return base64Texture;
} else {
plugin.logInfo("PlayerProfile中没有找到玩家 " + player.getName() + " 的皮肤URL");
}
} catch (Exception e) {
plugin.logWarning("从PlayerProfile获取皮肤失败: " + e.getMessage());
@@ -208,23 +324,92 @@ public class SkinManager {
return modelData;
}
/**
* 获取默认Steve皮肤的Base64纹理
*
* <p>当无法从任何来源获取玩家皮肤时使用默认的Steve皮肤作为备用。</p>
*
* @return 默认Steve皮肤的Base64纹理值
*/
private String getDefaultSteveSkin() {
return "8667ba71-b85a-4004-af54-457a9734eed7";
// Steve皮肤的纹理哈希值
String steveTextureHash = "8667ba71b85a4004af54457a9734eed7";
JsonObject textureJson = new JsonObject();
JsonObject texturesJson = new JsonObject();
JsonObject skinJson = new JsonObject();
// 使用Minecraft官方纹理服务器URL
skinJson.addProperty("url", "http://textures.minecraft.net/texture/" + steveTextureHash);
texturesJson.add("SKIN", skinJson);
textureJson.add("textures", texturesJson);
// 添加时间戳
textureJson.addProperty("timestamp", System.currentTimeMillis());
textureJson.addProperty("profileId", "c06f8906-4c8a-4911-9c29-ea1dbd1aab82"); // Steve的UUID
textureJson.addProperty("profileName", "Steve");
return java.util.Base64.getEncoder().encodeToString(textureJson.toString().getBytes());
}
/**
* 创建带有玩家皮肤的头颅物品
*
* <p>使用Base64纹理数据创建自定义玩家头颅支持离线服务器皮肤显示。</p>
*
* @param playerId 玩家UUID
* @param playerName 玩家名称
* @return 带有玩家皮肤的玩家头颅物品
*/
public ItemStack createPlayerHead(UUID playerId, String playerName) {
ItemStack head = new ItemStack(org.bukkit.Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (meta != null) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
meta.setOwningPlayer(offlinePlayer);
// 获取玩家的Base64皮肤数据
String skinBase64 = playerSkinData.get(playerId);
if (skinBase64 != null) {
try {
// 对于Paper API我们需要使用Paper特定的方法
// 首先尝试使用Paper的ProfileProperty API
Class<?> propertyClass = Class.forName("com.destroystokyo.paper.profile.ProfileProperty");
// 创建ProfileProperty对象
Object property = propertyClass.getConstructor(String.class, String.class)
.newInstance("textures", skinBase64);
// 获取Paper的PlayerProfile
Object profile = Bukkit.class.getMethod("createProfile", UUID.class, String.class)
.invoke(null, playerId, playerName);
// 设置属性到档案
Class<?> profileClass = profile.getClass();
profileClass.getMethod("setProperty", propertyClass).invoke(profile, property);
// 设置头颅的所有者档案 - 使用正确的类型转换
// Paper的SkullMeta.setPlayerProfile期望com.destroystokyo.paper.profile.PlayerProfile
meta.setPlayerProfile((com.destroystokyo.paper.profile.PlayerProfile) profile);
} catch (Exception e) {
// 如果反射失败,回退到使用离线玩家(可能显示默认皮肤)
plugin.logWarning("无法设置Base64皮肤使用离线玩家档案: " + e.getMessage());
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
meta.setOwningPlayer(offlinePlayer);
}
} else {
// 没有皮肤数据,使用离线玩家
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
meta.setOwningPlayer(offlinePlayer);
}
// 设置自定义模型数据(如果有)
Integer customModelData = playerCustomModelData.get(playerId);
if (customModelData != null) {
meta.setCustomModelData(customModelData);
}
// 设置显示名称和描述
meta.setDisplayName("§e" + playerName + "的生命方块");
List<String> lore = new ArrayList<>();