290 lines
10 KiB
Java
290 lines
10 KiB
Java
|
|
package com.playerblocklife;
|
|||
|
|
|
|||
|
|
import com.google.gson.JsonObject;
|
|||
|
|
import com.google.gson.JsonParser;
|
|||
|
|
import org.bukkit.Bukkit;
|
|||
|
|
import org.bukkit.OfflinePlayer;
|
|||
|
|
import org.bukkit.entity.Player;
|
|||
|
|
import org.bukkit.inventory.ItemStack;
|
|||
|
|
import org.bukkit.inventory.meta.SkullMeta;
|
|||
|
|
import org.bukkit.profile.PlayerProfile;
|
|||
|
|
import org.bukkit.profile.PlayerTextures;
|
|||
|
|
import org.yaml.snakeyaml.Yaml;
|
|||
|
|
|
|||
|
|
import javax.imageio.ImageIO;
|
|||
|
|
import java.awt.image.BufferedImage;
|
|||
|
|
import java.io.*;
|
|||
|
|
import java.net.URL;
|
|||
|
|
import java.nio.file.Files;
|
|||
|
|
import java.util.*;
|
|||
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|||
|
|
|
|||
|
|
public class SkinManager {
|
|||
|
|
private final PlayerBlockLife plugin;
|
|||
|
|
private final Map<UUID, String> playerSkinData = new ConcurrentHashMap<>();
|
|||
|
|
private final Map<UUID, Integer> playerCustomModelData = new ConcurrentHashMap<>();
|
|||
|
|
private final Map<UUID, Long> skinLoadTime = new ConcurrentHashMap<>();
|
|||
|
|
private final File skinDataFile;
|
|||
|
|
private final File skinCacheDir;
|
|||
|
|
private int nextCustomModelData = 1000;
|
|||
|
|
private static final long SKIN_CACHE_TIME = 7 * 24 * 60 * 60 * 1000L;
|
|||
|
|
|
|||
|
|
public SkinManager(PlayerBlockLife plugin) {
|
|||
|
|
this.plugin = plugin;
|
|||
|
|
this.skinDataFile = new File(plugin.getDataFolder(), "skindata.yml");
|
|||
|
|
this.skinCacheDir = new File(plugin.getDataFolder(), "skincache");
|
|||
|
|
|
|||
|
|
if (!skinCacheDir.exists()) {
|
|||
|
|
skinCacheDir.mkdirs();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void loadAllSkins() {
|
|||
|
|
loadSkinDataFromFile();
|
|||
|
|
Bukkit.getOnlinePlayers().forEach(this::loadPlayerSkinAsync);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void loadPlayerSkinAsync(Player player) {
|
|||
|
|
UUID playerId = player.getUniqueId();
|
|||
|
|
|
|||
|
|
if (playerSkinData.containsKey(playerId)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (loadSkinFromCache(playerId)) {
|
|||
|
|
plugin.logInfo("从缓存加载皮肤: " + player.getName());
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
|||
|
|
try {
|
|||
|
|
plugin.logInfo("开始加载皮肤: " + player.getName());
|
|||
|
|
|
|||
|
|
String skinBase64 = getSkinFromPlayerProfile(player);
|
|||
|
|
|
|||
|
|
if (skinBase64 == null) {
|
|||
|
|
skinBase64 = getDefaultSteveSkin();
|
|||
|
|
plugin.logWarning("使用默认皮肤: " + player.getName());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (skinBase64 != null) {
|
|||
|
|
playerSkinData.put(playerId, skinBase64);
|
|||
|
|
int modelData = allocateCustomModelData(playerId);
|
|||
|
|
saveSkinToCache(playerId, skinBase64);
|
|||
|
|
skinLoadTime.put(playerId, System.currentTimeMillis());
|
|||
|
|
|
|||
|
|
plugin.logInfo("皮肤加载完成: " + player.getName() + " (模型数据: " + modelData + ")");
|
|||
|
|
|
|||
|
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
|||
|
|
if (player.isOnline()) {
|
|||
|
|
player.sendMessage("§a你的皮肤方块已准备就绪!");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logError("加载皮肤失败: " + player.getName(), e);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private String getSkinFromPlayerProfile(Player player) {
|
|||
|
|
try {
|
|||
|
|
PlayerProfile profile = player.getPlayerProfile();
|
|||
|
|
PlayerTextures textures = profile.getTextures();
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logWarning("从PlayerProfile获取皮肤失败: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private boolean loadSkinFromCache(UUID playerId) {
|
|||
|
|
try {
|
|||
|
|
File cacheFile = new File(skinCacheDir, playerId.toString() + ".cache");
|
|||
|
|
if (cacheFile.exists()) {
|
|||
|
|
long lastModified = cacheFile.lastModified();
|
|||
|
|
if (System.currentTimeMillis() - lastModified < SKIN_CACHE_TIME) {
|
|||
|
|
String skinData = new String(Files.readAllBytes(cacheFile.toPath()));
|
|||
|
|
playerSkinData.put(playerId, skinData);
|
|||
|
|
allocateCustomModelData(playerId);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logWarning("读取皮肤缓存失败: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void saveSkinToCache(UUID playerId, String skinData) {
|
|||
|
|
try {
|
|||
|
|
File cacheFile = new File(skinCacheDir, playerId.toString() + ".cache");
|
|||
|
|
Files.write(cacheFile.toPath(), skinData.getBytes());
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logWarning("保存皮肤缓存失败: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private synchronized int allocateCustomModelData(UUID playerId) {
|
|||
|
|
if (playerCustomModelData.containsKey(playerId)) {
|
|||
|
|
return playerCustomModelData.get(playerId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int modelData = nextCustomModelData++;
|
|||
|
|
playerCustomModelData.put(playerId, modelData);
|
|||
|
|
return modelData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private String getDefaultSteveSkin() {
|
|||
|
|
return "8667ba71-b85a-4004-af54-457a9734eed7";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
Integer customModelData = playerCustomModelData.get(playerId);
|
|||
|
|
if (customModelData != null) {
|
|||
|
|
meta.setCustomModelData(customModelData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
meta.setDisplayName("§e" + playerName + "的生命方块");
|
|||
|
|
|
|||
|
|
List<String> lore = new ArrayList<>();
|
|||
|
|
lore.add("§7所有者: §e" + playerName);
|
|||
|
|
lore.add("§c⚠ 警告: 挖掘此方块将减少玩家生命值");
|
|||
|
|
lore.add("§7剩余生命: §a" + plugin.getBlockManager().getRemainingBlocks(playerId) + "/5");
|
|||
|
|
meta.setLore(lore);
|
|||
|
|
|
|||
|
|
head.setItemMeta(meta);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return head;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public boolean isSkinLoaded(UUID playerId) {
|
|||
|
|
return playerSkinData.containsKey(playerId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Integer getCustomModelData(UUID playerId) {
|
|||
|
|
return playerCustomModelData.get(playerId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Map<UUID, String> getAllSkinData() {
|
|||
|
|
return new HashMap<>(playerSkinData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Map<UUID, Integer> getAllCustomModelData() {
|
|||
|
|
return new HashMap<>(playerCustomModelData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void loadSkinDataFromFile() {
|
|||
|
|
if (!skinDataFile.exists()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
Yaml yaml = new Yaml();
|
|||
|
|
Map<String, Object> data = yaml.load(new FileReader(skinDataFile));
|
|||
|
|
|
|||
|
|
if (data != null && data.containsKey("skins")) {
|
|||
|
|
Map<String, Object> skins = (Map<String, Object>) data.get("skins");
|
|||
|
|
for (Map.Entry<String, Object> entry : skins.entrySet()) {
|
|||
|
|
UUID playerId = UUID.fromString(entry.getKey());
|
|||
|
|
String skinData = (String) entry.getValue();
|
|||
|
|
playerSkinData.put(playerId, skinData);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data != null && data.containsKey("modelData")) {
|
|||
|
|
Map<String, Integer> modelData = (Map<String, Integer>) data.get("modelData");
|
|||
|
|
for (Map.Entry<String, Integer> entry : modelData.entrySet()) {
|
|||
|
|
UUID playerId = UUID.fromString(entry.getKey());
|
|||
|
|
playerCustomModelData.put(playerId, entry.getValue());
|
|||
|
|
nextCustomModelData = Math.max(nextCustomModelData, entry.getValue() + 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
plugin.logInfo("已加载 " + playerSkinData.size() + " 个玩家的皮肤数据");
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logError("加载皮肤数据文件失败", e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void saveSkinData() {
|
|||
|
|
try {
|
|||
|
|
Map<String, Object> data = new HashMap<>();
|
|||
|
|
|
|||
|
|
Map<String, String> skins = new HashMap<>();
|
|||
|
|
for (Map.Entry<UUID, String> entry : playerSkinData.entrySet()) {
|
|||
|
|
skins.put(entry.getKey().toString(), entry.getValue());
|
|||
|
|
}
|
|||
|
|
data.put("skins", skins);
|
|||
|
|
|
|||
|
|
Map<String, Integer> modelData = new HashMap<>();
|
|||
|
|
for (Map.Entry<UUID, Integer> entry : playerCustomModelData.entrySet()) {
|
|||
|
|
modelData.put(entry.getKey().toString(), entry.getValue());
|
|||
|
|
}
|
|||
|
|
data.put("modelData", modelData);
|
|||
|
|
|
|||
|
|
Yaml yaml = new Yaml();
|
|||
|
|
yaml.dump(data, new FileWriter(skinDataFile));
|
|||
|
|
|
|||
|
|
plugin.logInfo("皮肤数据已保存");
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
plugin.logError("保存皮肤数据失败", e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void cleanupOldCache() {
|
|||
|
|
File[] cacheFiles = skinCacheDir.listFiles();
|
|||
|
|
if (cacheFiles != null) {
|
|||
|
|
long now = System.currentTimeMillis();
|
|||
|
|
int cleaned = 0;
|
|||
|
|
|
|||
|
|
for (File file : cacheFiles) {
|
|||
|
|
if (now - file.lastModified() > SKIN_CACHE_TIME) {
|
|||
|
|
if (file.delete()) {
|
|||
|
|
cleaned++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (cleaned > 0) {
|
|||
|
|
plugin.logInfo("清理了 " + cleaned + " 个过期皮肤缓存");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|