From 8c68028924a5218c2fc4fd48939961a0463cde84 Mon Sep 17 00:00:00 2001
From: xiaobai
Date: Sat, 14 Feb 2026 19:56:16 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A=EF=BC=88?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E6=A1=A3=E6=B3=A8=E9=87=8A=EF=BC=89?=
=?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0skinsrestorer=E7=9A=AE=E8=82=A4?=
=?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=94=AF=E6=8C=81=E5=8A=9F=E8=83=BD=EF=BC=88?=
=?UTF-8?q?=E6=9C=AA=E7=BB=8F=E5=AE=8C=E6=95=B4=E6=B5=8B=E8=AF=95=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 2 +-
gradle.properties | 2 +-
.../com/playerblocklife/ConfigManager.java | 122 +++++++-
.../java/com/playerblocklife/LifeSystem.java | 26 ++
.../com/playerblocklife/PlayerBlockLife.java | 65 +++++
.../playerblocklife/PlayerBlockManager.java | 97 ++++++-
.../java/com/playerblocklife/SkinManager.java | 271 +++++++++++++++---
src/main/resources/config.yml | 12 +-
src/main/resources/plugin.yml | 2 +-
9 files changed, 540 insertions(+), 59 deletions(-)
diff --git a/build.gradle b/build.gradle
index bcdb0cd..0934e8f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ plugins {
}
group = 'com.playerblocklife'
-version = '2.0.1-1.20.4'
+version = '3.0.0-experimental-1.20.4'
sourceCompatibility = 17
targetCompatibility = 17
diff --git a/gradle.properties b/gradle.properties
index 9b6fbcd..2f5e064 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,5 +5,5 @@ org.gradle.caching=true
org.gradle.daemon=true
# ????
-pluginVersion=2.0.1-1.20.4
+pluginVersion=2.1.0-1.20.4
mcVersion=1.20.4
\ No newline at end of file
diff --git a/src/main/java/com/playerblocklife/ConfigManager.java b/src/main/java/com/playerblocklife/ConfigManager.java
index 1fe2810..02ecead 100644
--- a/src/main/java/com/playerblocklife/ConfigManager.java
+++ b/src/main/java/com/playerblocklife/ConfigManager.java
@@ -9,18 +9,58 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
+/**
+ * 配置管理器 - 负责加载、保存和管理插件的配置文件
+ *
+ * 主要功能包括:
+ *
+ * - 加载和验证配置文件
+ * - 配置版本检查和自动更新
+ * - 提供类型安全的配置项访问方法
+ * - 处理配置文件的保存和重载
+ * - 管理SkinsRestorer插件集成配置
+ * - 支持多种皮肤来源的优先级配置
+ *
+ *
+ * SkinsRestorer配置支持:
+ *
+ * skin.source:皮肤来源优先级(skinsrestorer/player_profile/local_cache)
+ * skin.use-skinsrestorer:是否启用SkinsRestorer支持
+ * skin.cache.expire_days:皮肤缓存过期时间
+ * - 默认配置已优化,优先使用SkinsRestorer以支持离线服务器
+ *
+ *
+ *
+ * @author xiaobai
+ * @version 2.1.0
+ * @since 1.0.0
+ */
public class ConfigManager {
private final PlayerBlockLife plugin;
private FileConfiguration config;
private File configFile;
+ /**
+ * 构造一个新的配置管理器
+ *
+ * @param plugin 插件主类实例,用于访问插件相关功能
+ */
public ConfigManager(PlayerBlockLife plugin) {
this.plugin = plugin;
this.configFile = new File(plugin.getDataFolder(), "config.yml");
}
/**
- * 加载配置
+ * 加载插件配置
+ *
+ * 执行以下操作:
+ *
+ * - 确保插件数据文件夹存在
+ * - 如果配置文件不存在,从JAR中复制默认配置
+ * - 调用reloadConfig()重新加载配置
+ *
+ *
+ * @see #reloadConfig()
*/
public void loadConfig() {
// 确保配置文件夹存在
@@ -39,7 +79,16 @@ public class ConfigManager {
}
/**
- * 重新加载配置
+ * 重新加载配置文件
+ *
+ * 执行以下操作:
+ *
+ * - 从磁盘重新加载配置文件
+ * - 加载JAR中的默认配置作为后备
+ * - 检查配置版本并进行必要的更新
+ *
+ *
+ * @see #checkConfigVersion()
*/
public void reloadConfig() {
// 重新从磁盘加载配置
@@ -127,7 +176,12 @@ public class ConfigManager {
}
/**
- * 保存配置
+ * 保存当前配置到文件
+ *
+ * 将内存中的配置数据写入到config.yml文件中。
+ * 如果保存失败,会记录错误日志。
+ *
+ * @throws IOException 如果文件写入失败
*/
public void saveConfig() {
try {
@@ -139,6 +193,11 @@ public class ConfigManager {
/**
* 获取配置对象
+ *
+ * 如果配置对象为null,会自动调用reloadConfig()加载配置。
+ *
+ * @return 当前的FileConfiguration配置对象
+ * @see #reloadConfig()
*/
public FileConfiguration getConfig() {
if (config == null) {
@@ -185,12 +244,65 @@ public class ConfigManager {
return getConfig().getBoolean("skin.enabled", true);
}
+ /**
+ * 获取皮肤来源配置
+ *
+ * 支持的皮肤来源:
+ *
+ * - skinsrestorer:优先从SkinsRestorer插件获取皮肤纹理数据
+ *
+ * - 推荐用于离线服务器
+ * - 支持玩家自定义皮肤
+ * - 避免默认Steve皮肤问题
+ *
+ *
+ * - player_profile:优先使用Bukkit的PlayerProfile API
+ *
+ * - 需要玩家在线验证
+ * - 适合在线服务器
+ * - 支持Mojang官方皮肤
+ *
+ *
+ * - local_cache:优先从本地缓存加载皮肤数据
+ *
+ * - 减少网络请求
+ * - 提高加载速度
+ * - 支持离线使用
+ *
+ *
+ *
+ *
+ *
+ * 默认配置已将此值设为"skinsrestorer",以优化离线服务器体验。
+ *
+ * @return 皮肤来源配置值
+ * @see #useSkinsRestorer()
+ * @see SkinManager#loadPlayerSkinAsync()
+ */
public String getSkinSource() {
- return getConfig().getString("skin.source", "player_profile");
+ return getConfig().getString("skin.source", "skinsrestorer");
}
+ /**
+ * 检查是否启用SkinsRestorer插件支持
+ *
+ * 当此方法返回true时,插件将:
+ *
+ * - 优先从SkinsRestorer插件获取玩家皮肤纹理
+ * - 支持离线服务器获取玩家自定义皮肤
+ * - 避免方块总是显示默认Steve皮肤的问题
+ * - 使用反射安全调用SkinsRestorer API,无需硬依赖
+ *
+ *
+ *
+ * 默认配置已将此值设为true,以优化离线服务器体验。
+ *
+ * @return 如果启用SkinsRestorer支持返回true,否则返回false
+ * @see #getSkinSource()
+ * @see SkinManager#getSkinFromSkinsRestorer()
+ */
public boolean useSkinsRestorer() {
- return getConfig().getBoolean("skin.use-skinsrestorer", false);
+ return getConfig().getBoolean("skin.use-skinsrestorer", true);
}
public int getCacheExpireDays() {
diff --git a/src/main/java/com/playerblocklife/LifeSystem.java b/src/main/java/com/playerblocklife/LifeSystem.java
index 35d8939..425f5ed 100644
--- a/src/main/java/com/playerblocklife/LifeSystem.java
+++ b/src/main/java/com/playerblocklife/LifeSystem.java
@@ -10,6 +10,32 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+/**
+ * 生命值系统 - 负责管理玩家生命值和状态效果
+ *
+ * 根据玩家剩余生命方块数量计算生命值,并应用相应的状态效果:
+ *
+ * - 监控玩家剩余方块数量变化
+ * - 计算对应的生命值比例
+ * - 应用虚弱、缓慢、失明等负面效果
+ * - 处理玩家淘汰和复活逻辑
+ * - 提供生命值相关的音效和视觉反馈
+ *
+ *
+ * 生命值计算公式:
+ *
+ * 5个方块 → 20点生命值(满血)
+ * 4个方块 → 16点生命值
+ * 3个方块 → 12点生命值
+ * 2个方块 → 8点生命值
+ * 1个方块 → 4点生命值
+ * 0个方块 → 0点生命值(淘汰)
+ *
+ *
+ * @author xiaobai
+ * @version 2.1.0
+ * @since 1.0.0
+ */
public class LifeSystem {
private final PlayerBlockLife plugin;
private final Map playerHealth = new HashMap<>();
diff --git a/src/main/java/com/playerblocklife/PlayerBlockLife.java b/src/main/java/com/playerblocklife/PlayerBlockLife.java
index ad5b236..b72827d 100644
--- a/src/main/java/com/playerblocklife/PlayerBlockLife.java
+++ b/src/main/java/com/playerblocklife/PlayerBlockLife.java
@@ -5,6 +5,25 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level;
+/**
+ * PlayerBlockLife插件主类 - 玩家生命方块系统的核心控制器
+ *
+ * 这个插件为Minecraft服务器添加了一个独特的游戏机制:每个玩家拥有一定数量的生命方块,
+ * 这些方块使用玩家的皮肤作为材质。当其他玩家挖光某个玩家的所有生命方块时,该玩家会被淘汰。
+ *
+ * 主要功能:
+ *
+ * - 管理玩家生命方块的生成和销毁
+ * - 处理玩家皮肤的获取和应用
+ * - 监控玩家生命值状态
+ * - 提供完整的命令和权限系统
+ * - 支持配置热重载和数据持久化
+ *
+ *
+ * @author xiaobai
+ * @version 3.0.0-experimental-1.20.4
+ * @since 1.0.0
+ */
public class PlayerBlockLife extends JavaPlugin {
private static PlayerBlockLife instance;
private PlayerBlockManager blockManager;
@@ -13,6 +32,22 @@ public class PlayerBlockLife extends JavaPlugin {
private ConfigManager configManager;
private MessageManager messageManager;
+ /**
+ * 插件启用时调用,执行初始化操作
+ *
+ * 初始化流程:
+ *
+ * - 保存默认配置文件
+ * - 初始化所有管理器(注意依赖顺序)
+ * - 加载配置和消息数据
+ * - 注册事件监听器
+ * - 注册命令执行器
+ * - 加载玩家数据和皮肤缓存
+ * - 启动定时任务
+ *
+ *
+ * @see #onDisable()
+ */
@Override
public void onEnable() {
instance = this;
@@ -57,6 +92,18 @@ public class PlayerBlockLife extends JavaPlugin {
getLogger().info("§a========================================");
}
+ /**
+ * 插件禁用时调用,执行清理操作
+ *
+ * 执行以下清理操作:
+ *
+ * - 保存玩家方块数据到文件
+ * - 保存皮肤缓存数据
+ * - 记录插件禁用日志
+ *
+ *
+ * @see #onEnable()
+ */
@Override
public void onDisable() {
// 保存数据
@@ -81,6 +128,16 @@ public class PlayerBlockLife extends JavaPlugin {
/**
* 插件的完整重载方法(用于命令)
+ *
+ * 重新加载所有插件配置和数据,包括:
+ *
+ * - 主配置文件 (config.yml)
+ * - 消息配置文件 (messages.yml)
+ * - 玩家方块数据
+ * - 皮肤缓存数据
+ *
+ *
+ * 这个方法通常由管理员通过/pblreload命令调用。
*/
public void reloadPluginConfig() {
if (configManager != null) {
@@ -125,6 +182,14 @@ public class PlayerBlockLife extends JavaPlugin {
}, 1200L, 1200L);
}
+ /**
+ * 获取插件单例实例
+ *
+ * 提供全局访问点,允许其他类访问插件主实例。
+ *
+ * @return PlayerBlockLife插件实例
+ * @throws IllegalStateException 如果插件尚未启用(实例为null)
+ */
public static PlayerBlockLife getInstance() {
return instance;
}
diff --git a/src/main/java/com/playerblocklife/PlayerBlockManager.java b/src/main/java/com/playerblocklife/PlayerBlockManager.java
index 1ccb104..01ac9be 100644
--- a/src/main/java/com/playerblocklife/PlayerBlockManager.java
+++ b/src/main/java/com/playerblocklife/PlayerBlockManager.java
@@ -18,6 +18,34 @@ import java.io.FileWriter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+/**
+ * 玩家方块管理器 - 负责管理玩家生命方块的核心组件
+ *
+ * 主要职责:
+ *
+ * - 生成和放置玩家生命方块
+ * - 管理方块位置和所有者映射关系
+ * - 处理方块破坏和恢复逻辑
+ * - 提供方块数据持久化存储
+ * - 支持方块位置查询和验证
+ * - 与SkinManager协同工作,确保方块正确显示玩家皮肤
+ *
+ *
+ * SkinsRestorer集成特性:
+ *
+ * - 通过SkinManager获取SkinsRestorer提供的玩家皮肤纹理
+ * - 确保离线服务器上的方块显示正确的自定义皮肤
+ * - 支持异步皮肤加载,避免方块放置阻塞
+ * - 提供皮肤加载状态检查,确保皮肤就绪后再放置方块
+ *
+ *
+ *
+ * 使用并发安全的数据结构确保多线程环境下的数据一致性。
+ *
+ * @author xiaobai
+ * @version 2.1.0
+ * @since 1.0.0
+ */
public class PlayerBlockManager {
private final PlayerBlockLife plugin;
private final SkinManager skinManager;
@@ -47,7 +75,36 @@ public class PlayerBlockManager {
}
/**
- * 为玩家生成生命方块(新方法,支持自动生成)
+ * 为玩家生成指定数量的生命方块
+ *
+ * 此方法负责生成玩家的生命方块,包括以下步骤:
+ *
+ * - 检查玩家是否已有生命方块
+ * - 验证玩家皮肤是否已从SkinsRestorer或其他来源加载完成
+ * - 在指定范围内寻找合适的放置位置
+ * - 放置带有玩家皮肤纹理的玩家头颅方块
+ * - 记录方块位置和所有者关系
+ * - 保存数据并返回生成结果
+ *
+ *
+ *
+ * 皮肤加载检查:
+ *
+ * - 调用skinManager.isSkinLoaded()检查皮肤是否就绪
+ * - 如果皮肤未加载,方块生成将失败
+ * - 确保离线服务器通过SkinsRestorer获取的皮肤能正确应用
+ * - 避免放置默认Steve皮肤的方块
+ *
+ *
+ *
+ * @param player 目标玩家
+ * @param blockAmount 要生成的方块数量
+ * @param spreadRange 生成范围(以玩家为中心的正方形边长的一半)
+ * @param requireOpenSky 是否需要开阔天空(上方无方块覆盖)
+ * @param maxAttempts 寻找合适位置的最大尝试次数
+ * @return 生成成功返回true,失败返回false
+ * @see SkinManager#isSkinLoaded(UUID)
+ * @see SkinManager#getSkinFromSkinsRestorer(Player)
*/
public boolean generateLifeBlocksForPlayer(Player player, int blockAmount, int spreadRange, boolean requireOpenSky, int maxAttempts) {
UUID playerId = player.getUniqueId();
@@ -276,6 +333,13 @@ public class PlayerBlockManager {
/**
* 放置玩家头颅方块
+ *
+ * 使用SkinManager创建带有正确皮肤的玩家头颅方块,支持离线服务器皮肤显示。
+ *
+ * @param location 放置位置
+ * @param playerId 玩家UUID
+ * @param playerName 玩家名称
+ * @return 放置成功返回true,失败返回false
*/
private boolean placePlayerHead(Location location, UUID playerId, String playerName) {
try {
@@ -286,15 +350,37 @@ public class PlayerBlockManager {
return false;
}
+ // 检查玩家皮肤是否已加载
+ if (!skinManager.isSkinLoaded(playerId)) {
+ plugin.logWarning("玩家 " + playerName + " 的皮肤未加载,无法放置头颅方块");
+ return false;
+ }
+
// 设置方块为玩家头颅
block.setType(Material.PLAYER_HEAD);
// 获取并设置头颅数据
Skull skullState = (Skull) block.getState();
- OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
-
- // 设置头颅所有者
- skullState.setOwningPlayer(offlinePlayer);
+
+ // 使用SkinManager创建玩家头颅物品,然后应用到方块上
+ ItemStack headItem = skinManager.createPlayerHead(playerId, playerName);
+ SkullMeta itemMeta = (SkullMeta) headItem.getItemMeta();
+
+ if (itemMeta != null) {
+ // 获取物品的玩家档案并应用到方块上
+ org.bukkit.profile.PlayerProfile profile = itemMeta.getPlayerProfile();
+ if (profile != null) {
+ skullState.setOwnerProfile(profile);
+ } else {
+ // 如果无法获取档案,回退到使用离线玩家
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
+ skullState.setOwningPlayer(offlinePlayer);
+ }
+ } else {
+ // 如果物品元数据为空,使用离线玩家
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
+ skullState.setOwningPlayer(offlinePlayer);
+ }
// 设置朝向(随机方向)
BlockData blockData = block.getBlockData();
@@ -308,6 +394,7 @@ public class PlayerBlockManager {
// 更新方块
skullState.update(true, false);
+ plugin.logInfo("成功放置玩家头颅方块: " + playerName + " 在 " + location);
return true;
} catch (Exception e) {
plugin.logError("放置玩家头颅失败: " + location, e);
diff --git a/src/main/java/com/playerblocklife/SkinManager.java b/src/main/java/com/playerblocklife/SkinManager.java
index 4198cf0..c3ebd91 100644
--- a/src/main/java/com/playerblocklife/SkinManager.java
+++ b/src/main/java/com/playerblocklife/SkinManager.java
@@ -19,6 +19,42 @@ import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+/**
+ * 皮肤管理器 - 负责玩家皮肤的获取、缓存和应用
+ *
+ * 主要功能:
+ *
+ * - 从多种来源获取玩家皮肤数据(SkinsRestorer插件、PlayerProfile、本地缓存)
+ * - 皮肤数据Base64编码和缓存管理
+ * - 自定义模型数据分配和管理
+ * - 异步皮肤加载避免阻塞主线程
+ * - 皮肤缓存过期清理
+ * - 完整的SkinsRestorer插件集成支持
+ *
+ *
+ * 皮肤获取优先级(根据配置的source字段):
+ *
+ * - skinsrestorer:优先从SkinsRestorer插件获取皮肤纹理数据
+ * - player_profile:优先使用Bukkit的PlayerProfile API
+ * - local_cache:优先从本地缓存加载
+ * - 默认Steve皮肤(所有来源都失败时的备用)
+ *
+ *
+ * SkinsRestorer集成特性:
+ *
+ * - 自动检测SkinsRestorer插件是否安装
+ * - 使用反射安全调用SkinsRestorer API,避免硬依赖
+ * - 获取完整的皮肤纹理数据(value和signature)
+ * - 支持离线服务器,避免默认Steve皮肤问题
+ * - 优雅降级:SkinsRestorer失败时自动回退到其他来源
+ *
+ *
+ * 皮肤缓存默认保留7天,过期后自动重新获取。
+ *
+ * @author xiaobai
+ * @version 2.1.0
+ * @since 1.0.0
+ */
public class SkinManager {
private final PlayerBlockLife plugin;
private final Map 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插件获取玩家皮肤纹理数据
+ *
+ * SkinsRestorer是一个流行的皮肤管理插件,可以在离线服务器上提供皮肤支持。
+ *
+ * 此方法使用反射安全调用SkinsRestorer API,避免硬依赖。支持离线服务器获取玩家自定义皮肤。
+ *
+ * 获取流程:
+ *
+ * - 检查SkinsRestorer插件是否安装
+ * - 使用反射获取SkinsRestorer API实例
+ * - 优先通过UUID获取皮肤数据(更可靠)
+ * - 如果UUID获取失败,回退到使用玩家名获取
+ * - 提取皮肤纹理的value和signature字段
+ * - 构建完整的Base64编码纹理JSON
+ *
+ *
+ *
+ * 离线服务器优势:
+ *
+ * - 即使玩家离线也能获取其预设皮肤
+ * - 避免总是显示默认Steve皮肤的问题
+ * - 支持管理员设置的皮肤和玩家自定义皮肤
+ *
+ *
+ *
+ * @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获取玩家皮肤数据
+ *
+ * 使用Bukkit的PlayerProfile API获取在线玩家的皮肤URL,然后转换为Base64纹理。
+ *
+ * @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纹理
+ *
+ * 当无法从任何来源获取玩家皮肤时,使用默认的Steve皮肤作为备用。
+ *
+ * @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());
}
+ /**
+ * 创建带有玩家皮肤的头颅物品
+ *
+ * 使用Base64纹理数据创建自定义玩家头颅,支持离线服务器皮肤显示。
+ *
+ * @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 lore = new ArrayList<>();
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 3199f6e..b2bde41 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -44,9 +44,15 @@ skin:
# 是否启用皮肤系统
enabled: true
# 皮肤来源 (player_profile, local_cache, skinsrestorer)
- source: player_profile
- # 是否使用SkinsRestorer插件皮肤(如果服务器有此插件)
- use-skinsrestorer: false
+ # player_profile: 使用Bukkit的PlayerProfile API(需要在线验证)
+ # local_cache: 使用本地缓存的皮肤数据
+ # skinsrestorer: 使用SkinsRestorer插件的皮肤数据(推荐用于离线服务器)
+ source: skinsrestorer
+
+ # 是否使用SkinsRestorer插件的皮肤(如果服务器有此插件)
+ # 设置为true时,插件会优先从SkinsRestorer获取皮肤数据
+ # 这对于离线服务器特别有用,可以避免默认Steve皮肤的问题
+ use-skinsrestorer: true
# 缓存设置
cache:
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index f11d9b6..4d63579 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,5 +1,5 @@
name: PlayerBlockLife
-version: 2.0.1-1.20.4
+version: 3.0.0-experimental-1.20.4
main: com.playerblocklife.PlayerBlockLife
api-version: 1.20
author: xiaobai