810 lines
27 KiB
Java
810 lines
27 KiB
Java
package com.playerblocklife;
|
||
|
||
import org.bukkit.*;
|
||
import org.bukkit.block.Block;
|
||
import org.bukkit.block.BlockFace;
|
||
import org.bukkit.block.Skull;
|
||
import org.bukkit.block.data.BlockData;
|
||
import org.bukkit.block.data.Rotatable;
|
||
import org.bukkit.entity.Player;
|
||
import org.bukkit.inventory.ItemStack;
|
||
import org.bukkit.inventory.meta.SkullMeta;
|
||
import org.bukkit.util.Vector;
|
||
import org.yaml.snakeyaml.Yaml;
|
||
|
||
import java.io.File;
|
||
import java.io.FileReader;
|
||
import java.io.FileWriter;
|
||
import java.util.*;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
|
||
/**
|
||
* 玩家方块管理器 - 负责管理玩家生命方块的核心组件
|
||
*
|
||
* <p>主要职责:
|
||
* <ul>
|
||
* <li>生成和放置玩家生命方块</li>
|
||
* <li>管理方块位置和所有者映射关系</li>
|
||
* <li>处理方块破坏和恢复逻辑</li>
|
||
* <li>提供方块数据持久化存储</li>
|
||
* <li>支持方块位置查询和验证</li>
|
||
* <li>与SkinManager协同工作,确保方块正确显示玩家皮肤</li>
|
||
* </ul>
|
||
*
|
||
* <p><b>SkinsRestorer集成特性:</b>
|
||
* <ul>
|
||
* <li>通过SkinManager获取SkinsRestorer提供的玩家皮肤纹理</li>
|
||
* <li>确保离线服务器上的方块显示正确的自定义皮肤</li>
|
||
* <li>支持异步皮肤加载,避免方块放置阻塞</li>
|
||
* <li>提供皮肤加载状态检查,确保皮肤就绪后再放置方块</li>
|
||
* </ul>
|
||
*
|
||
* <p>使用并发安全的数据结构确保多线程环境下的数据一致性。</p>
|
||
*
|
||
* @author xiaobai
|
||
* @version 2.2.2
|
||
* @since 1.0.0
|
||
*/
|
||
public class PlayerBlockManager {
|
||
private final PlayerBlockLife plugin;
|
||
private final SkinManager skinManager;
|
||
private final Map<UUID, List<Location>> playerBlocks = new ConcurrentHashMap<>();
|
||
private final Map<Location, UUID> blockOwners = new ConcurrentHashMap<>();
|
||
private final Map<UUID, Material> playerBlockTypes = new ConcurrentHashMap<>();
|
||
private final File dataFile;
|
||
private final Random random = new Random();
|
||
|
||
public PlayerBlockManager(PlayerBlockLife plugin, SkinManager skinManager) {
|
||
this.plugin = plugin;
|
||
this.skinManager = skinManager;
|
||
this.dataFile = new File(plugin.getDataFolder(), "blockdata.yml");
|
||
}
|
||
|
||
/**
|
||
* 为玩家设置生命方块(兼容旧方法)
|
||
*/
|
||
public boolean setLifeBlocks(Player player, Location center) {
|
||
ConfigManager config = plugin.getConfigManager();
|
||
boolean requireOpenSky = true; // 使用默认值,因为配置已移除
|
||
int maxAttempts = 50; // 使用默认值,因为配置已移除
|
||
int spreadRange = config.getSpreadRange(); // 从配置获取范围
|
||
int minDistance = config.getMinDistance(); // 从配置获取最小距离
|
||
|
||
return generateLifeBlocksForPlayer(player, 5, spreadRange, minDistance, requireOpenSky, maxAttempts);
|
||
}
|
||
|
||
/**
|
||
* 为玩家生成指定数量的生命方块
|
||
*
|
||
* <p>此方法负责生成玩家的生命方块,包括以下步骤:
|
||
* <ol>
|
||
* <li>检查玩家是否已有生命方块</li>
|
||
* <li>验证玩家皮肤是否已从SkinsRestorer或其他来源加载完成</li>
|
||
* <li>在指定范围内寻找合适的放置位置</li>
|
||
* <li>放置带有玩家皮肤纹理的玩家头颅方块</li>
|
||
* <li>记录方块位置和所有者关系</li>
|
||
* <li>保存数据并返回生成结果</li>
|
||
* </ol>
|
||
* </p>
|
||
*
|
||
* <p><b>皮肤加载检查:</b>
|
||
* <ul>
|
||
* <li>调用skinManager.isSkinLoaded()检查皮肤是否就绪</li>
|
||
* <li>如果皮肤未加载,方块生成将失败</li>
|
||
* <li>确保离线服务器通过SkinsRestorer获取的皮肤能正确应用</li>
|
||
* <li>避免放置默认Steve皮肤的方块</li>
|
||
* </ul>
|
||
* </p>
|
||
*
|
||
* @param player 目标玩家
|
||
* @param blockAmount 要生成的方块数量
|
||
* @param spreadRange 生成范围(以玩家为中心的半径)
|
||
* @param minDistance 生成方块距离玩家的最小距离
|
||
* @param requireOpenSky 是否需要开阔天空(上方无方块覆盖)
|
||
* @param maxAttempts 寻找合适位置的最大尝试次数
|
||
* @return 生成成功返回true,失败返回false
|
||
* @see SkinManager#isSkinLoaded(UUID)
|
||
* @see SkinManager#getSkinFromSkinsRestorer(Player)
|
||
*/
|
||
public boolean generateLifeBlocksForPlayer(Player player, int blockAmount, int spreadRange, int minDistance, boolean requireOpenSky, int maxAttempts) {
|
||
UUID playerId = player.getUniqueId();
|
||
String playerName = player.getName();
|
||
|
||
// 检查是否已有生命方块
|
||
if (hasLifeBlocks(playerId)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查玩家皮肤是否已加载
|
||
if (!skinManager.isSkinLoaded(playerId)) {
|
||
return false;
|
||
}
|
||
|
||
List<Location> blocks = new ArrayList<>();
|
||
int blocksPlaced = 0;
|
||
int attempts = 0;
|
||
|
||
// 尝试生成指定数量的方块
|
||
while (blocksPlaced < blockAmount && attempts < maxAttempts) {
|
||
Location blockLoc = findSurfaceLocation(player.getLocation(), spreadRange, minDistance, requireOpenSky);
|
||
attempts++;
|
||
|
||
if (blockLoc != null && placePlayerHead(blockLoc, playerId, playerName)) {
|
||
blocks.add(blockLoc);
|
||
blockOwners.put(blockLoc, playerId);
|
||
blocksPlaced++;
|
||
|
||
// 添加放置效果
|
||
spawnPlaceEffects(blockLoc);
|
||
}
|
||
}
|
||
|
||
if (blocksPlaced > 0) {
|
||
playerBlocks.put(playerId, blocks);
|
||
saveData();
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 寻找地表位置(放宽条件,只要是露天地面就可以)
|
||
*/
|
||
private Location findSurfaceLocation(Location center, int spreadRange, int minDistance, boolean requireOpenSky) {
|
||
for (int i = 0; i < 20; i++) { // 增加尝试次数
|
||
int x = random.nextInt(spreadRange * 2 + 1) - spreadRange;
|
||
int z = random.nextInt(spreadRange * 2 + 1) - spreadRange;
|
||
|
||
// 检查与玩家的距离是否满足最小距离要求
|
||
double distance = Math.sqrt(x * x + z * z);
|
||
if (distance < minDistance) {
|
||
// 如果距离太近,重新生成坐标
|
||
continue;
|
||
}
|
||
|
||
// 以玩家坐标为中心,但高度从世界最高点开始寻找
|
||
Location testLoc = center.clone().add(x, 0, z);
|
||
World world = testLoc.getWorld();
|
||
if (world == null) continue;
|
||
|
||
// 从世界最高点向下寻找第一个固体方块
|
||
int maxHeight = world.getMaxHeight();
|
||
Block groundBlock = null;
|
||
for (int y = maxHeight; y > world.getMinHeight(); y--) {
|
||
testLoc.setY(y);
|
||
Block block = testLoc.getBlock();
|
||
Material type = block.getType();
|
||
|
||
// 检查是否是固体方块(可以作为支撑)
|
||
if (type.isSolid() && type.isBlock() && !type.isTransparent()) {
|
||
groundBlock = block;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (groundBlock == null) continue;
|
||
|
||
// 地表位置 = 固体方块上方一格
|
||
Location surfaceLoc = groundBlock.getLocation().add(0, 1, 0);
|
||
|
||
// 检查是否已有方块
|
||
if (blockOwners.containsKey(surfaceLoc)) {
|
||
continue;
|
||
}
|
||
|
||
// 检查位置是否合适(放宽条件)
|
||
if (!isSuitableLocationRelaxed(surfaceLoc)) {
|
||
continue;
|
||
}
|
||
|
||
// 如果需要上方无方块覆盖,检查上方3格
|
||
if (requireOpenSky) {
|
||
boolean hasCover = false;
|
||
for (int y = 1; y <= 3; y++) {
|
||
Block aboveBlock = surfaceLoc.clone().add(0, y, 0).getBlock();
|
||
if (!aboveBlock.getType().isAir()) {
|
||
hasCover = true;
|
||
break;
|
||
}
|
||
}
|
||
if (hasCover) continue;
|
||
}
|
||
|
||
return surfaceLoc;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 寻找合适的位置
|
||
*/
|
||
private Location findSuitableLocation(Location center) {
|
||
for (int i = 0; i < 10; i++) {
|
||
int x = random.nextInt(11) - 5; // -5 到 5
|
||
int z = random.nextInt(11) - 5;
|
||
|
||
Location testLoc = center.clone().add(x, -1, z);
|
||
Block block = testLoc.getBlock();
|
||
|
||
// 检查位置是否合适
|
||
if (isSuitableLocation(testLoc)) {
|
||
return testLoc;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 检查位置是否合适
|
||
* 修复:移除 isReplaceable() 方法调用
|
||
*/
|
||
private boolean isSuitableLocation(Location location) {
|
||
Block block = location.getBlock();
|
||
|
||
// 检查是否已有方块
|
||
if (blockOwners.containsKey(location)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查方块是否可替换(修复了 isReplaceable() 方法不存在的问题)
|
||
Material type = block.getType();
|
||
if (!type.isAir()) {
|
||
// 检查是否是固体方块,固体方块不能替换
|
||
if (type.isSolid()) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否是液体
|
||
if (type == Material.WATER || type == Material.LAVA ||
|
||
type == Material.WATER_CAULDRON || type == Material.LAVA_CAULDRON) {
|
||
return false;
|
||
}
|
||
|
||
// 检查一些特定的不可替换方块
|
||
switch (type) {
|
||
case FIRE:
|
||
case SOUL_FIRE:
|
||
case COBWEB:
|
||
case BAMBOO:
|
||
case BAMBOO_SAPLING:
|
||
case SCAFFOLDING:
|
||
case LADDER:
|
||
case VINE:
|
||
case TWISTING_VINES:
|
||
case WEEPING_VINES:
|
||
case GLOW_LICHEN:
|
||
// 这些方块可以替换
|
||
break;
|
||
default:
|
||
// 如果不是固体,也不是特定的透明方块,也不是空气,则检查是否是透明方块
|
||
if (!type.isTransparent()) {
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 检查下方是否有支撑方块
|
||
Block below = location.clone().add(0, -1, 0).getBlock();
|
||
Material belowType = below.getType();
|
||
|
||
if (belowType.isAir() || !belowType.isSolid()) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否在水或岩浆中
|
||
if (block.isLiquid() || below.isLiquid()) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 放宽条件的检查方法(用于新的生成逻辑)
|
||
*/
|
||
private boolean isSuitableLocationRelaxed(Location location) {
|
||
Block block = location.getBlock();
|
||
|
||
// 检查是否已有方块
|
||
if (blockOwners.containsKey(location)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查方块是否可替换(放宽条件)
|
||
Material type = block.getType();
|
||
if (!type.isAir()) {
|
||
// 固体方块不能替换
|
||
if (type.isSolid()) {
|
||
return false;
|
||
}
|
||
|
||
// 液体方块不能替换
|
||
if (type == Material.WATER || type == Material.LAVA) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 检查下方是否有支撑方块(放宽条件)
|
||
Block below = location.clone().add(0, -1, 0).getBlock();
|
||
Material belowType = below.getType();
|
||
|
||
// 只要下方不是空气或液体就可以
|
||
if (belowType.isAir() || belowType == Material.WATER || belowType == Material.LAVA) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 放置玩家头颅方块
|
||
*
|
||
* <p>使用SkinManager创建带有正确皮肤的玩家头颅方块,支持离线服务器皮肤显示。</p>
|
||
*
|
||
* @param location 放置位置
|
||
* @param playerId 玩家UUID
|
||
* @param playerName 玩家名称
|
||
* @return 放置成功返回true,失败返回false
|
||
*/
|
||
private boolean placePlayerHead(Location location, UUID playerId, String playerName) {
|
||
try {
|
||
Block block = location.getBlock();
|
||
|
||
// 检查方块是否已被占用
|
||
if (blockOwners.containsKey(location)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查玩家皮肤是否已加载
|
||
if (!skinManager.isSkinLoaded(playerId)) {
|
||
plugin.logWarning("玩家 " + playerName + " 的皮肤未加载,无法放置头颅方块");
|
||
return false;
|
||
}
|
||
|
||
// 设置方块为玩家头颅
|
||
block.setType(Material.PLAYER_HEAD);
|
||
|
||
// 获取并设置头颅数据
|
||
Skull skullState = (Skull) block.getState();
|
||
|
||
// 使用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();
|
||
if (blockData instanceof Rotatable) {
|
||
Rotatable rotatable = (Rotatable) blockData;
|
||
BlockFace[] faces = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST};
|
||
rotatable.setRotation(faces[random.nextInt(faces.length)]);
|
||
block.setBlockData(rotatable);
|
||
}
|
||
|
||
// 更新方块
|
||
skullState.update(true, false);
|
||
|
||
plugin.logInfo("成功放置玩家头颅方块: " + playerName + " 在 " + location);
|
||
return true;
|
||
} catch (Exception e) {
|
||
plugin.logError("放置玩家头颅失败: " + location, e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成放置效果
|
||
*/
|
||
private void spawnPlaceEffects(Location location) {
|
||
World world = location.getWorld();
|
||
if (world == null) return;
|
||
|
||
// 粒子效果
|
||
world.spawnParticle(Particle.ENCHANTMENT_TABLE,
|
||
location.clone().add(0.5, 0.5, 0.5),
|
||
30, 0.3, 0.3, 0.3, 0.1);
|
||
|
||
// 音效
|
||
world.playSound(location, Sound.BLOCK_ANVIL_PLACE, 0.5f, 1.2f);
|
||
}
|
||
|
||
/**
|
||
* 检查方块是否属于某个玩家
|
||
*/
|
||
public UUID getBlockOwner(Location location) {
|
||
return blockOwners.get(location);
|
||
}
|
||
|
||
/**
|
||
* 移除方块(当被挖掘时)
|
||
*/
|
||
public boolean removeBlock(Location location, Player breaker) {
|
||
UUID ownerId = blockOwners.get(location);
|
||
if (ownerId == null) {
|
||
return false;
|
||
}
|
||
|
||
List<Location> blocks = playerBlocks.get(ownerId);
|
||
if (blocks == null || !blocks.contains(location)) {
|
||
return false;
|
||
}
|
||
|
||
// 移除方块
|
||
blocks.remove(location);
|
||
blockOwners.remove(location);
|
||
|
||
// 设置方块为空气
|
||
location.getBlock().setType(Material.AIR);
|
||
|
||
// 生成破坏效果
|
||
spawnBreakEffects(location, breaker);
|
||
|
||
// 检查玩家是否还有剩余方块
|
||
int remaining = blocks.size();
|
||
|
||
// 通知所有相关玩家
|
||
notifyBlockBreak(ownerId, breaker, remaining);
|
||
|
||
// 保存数据
|
||
saveData();
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 生成破坏效果
|
||
*/
|
||
private void spawnBreakEffects(Location location, Player breaker) {
|
||
World world = location.getWorld();
|
||
if (world == null) return;
|
||
|
||
// 粒子效果
|
||
world.spawnParticle(Particle.BLOCK_CRACK,
|
||
location.clone().add(0.5, 0.5, 0.5),
|
||
50, 0.5, 0.5, 0.5, 0.5, Material.PLAYER_HEAD.createBlockData());
|
||
|
||
world.spawnParticle(Particle.SMOKE_LARGE,
|
||
location.clone().add(0.5, 0.5, 0.5),
|
||
20, 0.3, 0.3, 0.3, 0.05);
|
||
|
||
// 音效
|
||
world.playSound(location, Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f);
|
||
world.playSound(location, Sound.BLOCK_GLASS_BREAK, 0.8f, 1.0f);
|
||
|
||
// 对挖掘者造成轻微击退
|
||
if (breaker != null) {
|
||
Location breakerLoc = breaker.getLocation();
|
||
Vector direction = location.toVector().subtract(breakerLoc.toVector()).normalize();
|
||
breaker.setVelocity(direction.multiply(-0.5).setY(0.3));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通知方块被破坏
|
||
*/
|
||
private void notifyBlockBreak(UUID ownerId, Player breaker, int remaining) {
|
||
Player owner = Bukkit.getPlayer(ownerId);
|
||
String ownerName = Bukkit.getOfflinePlayer(ownerId).getName();
|
||
|
||
// 通知方块所有者
|
||
if (owner != null && owner.isOnline()) {
|
||
owner.sendMessage("§c⚠ 警告!你的生命方块被破坏了!");
|
||
owner.sendMessage("§7破坏者: §e" + (breaker != null ? breaker.getName() : "未知"));
|
||
owner.sendMessage("§7剩余生命方块: §a" + remaining + " §7/ §c5");
|
||
|
||
if (remaining <= 2) {
|
||
owner.sendMessage("§4⚠ 警告!生命方块即将耗尽!");
|
||
}
|
||
|
||
// 播放警告音效
|
||
owner.playSound(owner.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.8f, 0.5f);
|
||
}
|
||
|
||
// 通知破坏者
|
||
if (breaker != null && !breaker.getUniqueId().equals(ownerId)) {
|
||
breaker.sendMessage("§6你破坏了一个生命方块!");
|
||
breaker.sendMessage("§7所有者: §e" + (ownerName != null ? ownerName : "未知玩家"));
|
||
breaker.sendMessage("§7对方剩余生命方块: §a" + remaining);
|
||
|
||
// 给予挖掘者经验奖励
|
||
breaker.giveExp(5);
|
||
breaker.playSound(breaker.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
|
||
}
|
||
|
||
// 广播给附近玩家
|
||
World world = owner != null ? owner.getWorld() : (breaker != null ? breaker.getWorld() : null);
|
||
if (world != null) {
|
||
for (Player nearby : world.getPlayers()) {
|
||
if (nearby != owner && nearby != breaker &&
|
||
nearby.getLocation().distance(owner != null ? owner.getLocation() : breaker.getLocation()) < 30) {
|
||
nearby.sendMessage("§7[附近] §e一个生命方块被破坏了!");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取玩家剩余方块数量
|
||
*/
|
||
public int getRemainingBlocks(UUID playerId) {
|
||
List<Location> blocks = playerBlocks.get(playerId);
|
||
return blocks != null ? blocks.size() : 0;
|
||
}
|
||
|
||
/**
|
||
* 检查玩家是否有生命方块
|
||
*/
|
||
public boolean hasLifeBlocks(UUID playerId) {
|
||
List<Location> blocks = playerBlocks.get(playerId);
|
||
return blocks != null && !blocks.isEmpty();
|
||
}
|
||
|
||
/**
|
||
* 获取玩家的所有生命方块位置
|
||
*/
|
||
public List<Location> getPlayerBlocks(UUID playerId) {
|
||
return playerBlocks.getOrDefault(playerId, new ArrayList<>());
|
||
}
|
||
|
||
/**
|
||
* 清除玩家的所有生命方块
|
||
*/
|
||
public void clearPlayerBlocks(UUID playerId) {
|
||
List<Location> blocks = playerBlocks.remove(playerId);
|
||
if (blocks != null) {
|
||
for (Location loc : blocks) {
|
||
blockOwners.remove(loc);
|
||
loc.getBlock().setType(Material.AIR);
|
||
}
|
||
}
|
||
saveData();
|
||
}
|
||
|
||
/**
|
||
* 重新生成玩家的生命方块
|
||
*/
|
||
public boolean regeneratePlayerBlocks(Player player) {
|
||
UUID playerId = player.getUniqueId();
|
||
|
||
// 清除旧方块
|
||
clearPlayerBlocks(playerId);
|
||
|
||
// 生成新方块
|
||
return setLifeBlocks(player, player.getLocation());
|
||
}
|
||
|
||
/**
|
||
* 获取已注册玩家的数量
|
||
*/
|
||
public int getPlayerBlocksCount() {
|
||
return playerBlocks.size();
|
||
}
|
||
|
||
/**
|
||
* 获取总方块数量
|
||
*/
|
||
public int getTotalBlocksCount() {
|
||
int total = 0;
|
||
for (List<Location> blocks : playerBlocks.values()) {
|
||
total += blocks.size();
|
||
}
|
||
return total;
|
||
}
|
||
|
||
/**
|
||
* 加载数据
|
||
*/
|
||
@SuppressWarnings("unchecked")
|
||
public void loadData() {
|
||
if (!dataFile.exists()) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Yaml yaml = new Yaml();
|
||
Map<String, Object> data = yaml.load(new FileReader(dataFile));
|
||
|
||
if (data == null) {
|
||
return;
|
||
}
|
||
|
||
// 加载方块数据
|
||
if (data.containsKey("blocks")) {
|
||
Map<String, List<Map<String, Object>>> blocksData =
|
||
(Map<String, List<Map<String, Object>>>) data.get("blocks");
|
||
|
||
for (Map.Entry<String, List<Map<String, Object>>> entry : blocksData.entrySet()) {
|
||
UUID playerId = UUID.fromString(entry.getKey());
|
||
List<Location> locations = new ArrayList<>();
|
||
|
||
for (Map<String, Object> locData : entry.getValue()) {
|
||
String worldName = (String) locData.get("world");
|
||
double x = (double) locData.get("x");
|
||
double y = (double) locData.get("y");
|
||
double z = (double) locData.get("z");
|
||
|
||
World world = Bukkit.getWorld(worldName);
|
||
if (world != null) {
|
||
Location location = new Location(world, x, y, z);
|
||
locations.add(location);
|
||
blockOwners.put(location, playerId);
|
||
}
|
||
}
|
||
|
||
if (!locations.isEmpty()) {
|
||
playerBlocks.put(playerId, locations);
|
||
}
|
||
}
|
||
}
|
||
|
||
plugin.logInfo("已加载 " + playerBlocks.size() + " 个玩家的方块数据");
|
||
plugin.logInfo("总共 " + blockOwners.size() + " 个生命方块");
|
||
|
||
} catch (Exception e) {
|
||
plugin.logError("加载方块数据失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存数据
|
||
*/
|
||
public void saveData() {
|
||
try {
|
||
Map<String, Object> data = new HashMap<>();
|
||
Map<String, List<Map<String, Object>>> blocksData = new HashMap<>();
|
||
|
||
// 保存方块数据
|
||
for (Map.Entry<UUID, List<Location>> entry : playerBlocks.entrySet()) {
|
||
String playerId = entry.getKey().toString();
|
||
List<Map<String, Object>> locations = new ArrayList<>();
|
||
|
||
for (Location loc : entry.getValue()) {
|
||
Map<String, Object> locData = new HashMap<>();
|
||
locData.put("world", loc.getWorld().getName());
|
||
locData.put("x", loc.getX());
|
||
locData.put("y", loc.getY());
|
||
locData.put("z", loc.getZ());
|
||
locations.add(locData);
|
||
}
|
||
|
||
blocksData.put(playerId, locations);
|
||
}
|
||
|
||
data.put("blocks", blocksData);
|
||
|
||
// 写入文件
|
||
Yaml yaml = new Yaml();
|
||
yaml.dump(data, new FileWriter(dataFile));
|
||
|
||
} catch (Exception e) {
|
||
plugin.logError("保存方块数据失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有玩家的方块数据
|
||
*/
|
||
public Map<UUID, List<Location>> getAllPlayerBlocks() {
|
||
return new HashMap<>(playerBlocks);
|
||
}
|
||
|
||
/**
|
||
* 获取所有方块的位置和所有者
|
||
*/
|
||
public Map<Location, UUID> getAllBlockOwners() {
|
||
return new HashMap<>(blockOwners);
|
||
}
|
||
|
||
/**
|
||
* 检查位置是否包含生命方块
|
||
*/
|
||
public boolean isLifeBlock(Location location) {
|
||
return blockOwners.containsKey(location);
|
||
}
|
||
|
||
/**
|
||
* 获取玩家的生命方块位置列表(用于显示)
|
||
*/
|
||
public List<String> getPlayerBlockLocations(UUID playerId) {
|
||
List<Location> blocks = playerBlocks.get(playerId);
|
||
List<String> locations = new ArrayList<>();
|
||
|
||
if (blocks != null) {
|
||
for (Location loc : blocks) {
|
||
String worldName = loc.getWorld() != null ? loc.getWorld().getName() : "未知世界";
|
||
locations.add(String.format("世界: %s, 坐标: %d, %d, %d",
|
||
worldName,
|
||
loc.getBlockX(),
|
||
loc.getBlockY(),
|
||
loc.getBlockZ()));
|
||
}
|
||
}
|
||
|
||
return locations;
|
||
}
|
||
|
||
/**
|
||
* 获取距离玩家最近的方块
|
||
*/
|
||
public Location getNearestBlock(Player player) {
|
||
UUID playerId = player.getUniqueId();
|
||
List<Location> blocks = playerBlocks.get(playerId);
|
||
|
||
if (blocks == null || blocks.isEmpty()) {
|
||
return null;
|
||
}
|
||
|
||
Location nearest = null;
|
||
double nearestDistance = Double.MAX_VALUE;
|
||
|
||
for (Location block : blocks) {
|
||
double distance = player.getLocation().distance(block);
|
||
if (distance < nearestDistance) {
|
||
nearestDistance = distance;
|
||
nearest = block;
|
||
}
|
||
}
|
||
|
||
return nearest;
|
||
}
|
||
|
||
/**
|
||
* 重置游戏时删除所有未被破坏的生命方块
|
||
*/
|
||
public void resetGameBlocks() {
|
||
// 遍历所有生命方块位置并将其设置为空气
|
||
for (Location location : blockOwners.keySet()) {
|
||
if (location.getWorld() != null) {
|
||
Block block = location.getBlock();
|
||
// 只删除玩家头颅方块
|
||
if (block.getType() == Material.PLAYER_HEAD || block.getType() == Material.PLAYER_WALL_HEAD) {
|
||
block.setType(Material.AIR);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清空内部数据结构
|
||
playerBlocks.clear();
|
||
blockOwners.clear();
|
||
playerBlockTypes.clear();
|
||
|
||
// 保存数据更新
|
||
saveData();
|
||
}
|
||
|
||
/**
|
||
* 获取所有生命方块的统计信息
|
||
*/
|
||
public Map<String, Object> getStats() {
|
||
Map<String, Object> stats = new HashMap<>();
|
||
stats.put("totalPlayers", playerBlocks.size());
|
||
stats.put("totalBlocks", blockOwners.size());
|
||
|
||
// 按世界统计
|
||
Map<String, Integer> blocksPerWorld = new HashMap<>();
|
||
for (Location loc : blockOwners.keySet()) {
|
||
String worldName = loc.getWorld() != null ? loc.getWorld().getName() : "unknown";
|
||
blocksPerWorld.put(worldName, blocksPerWorld.getOrDefault(worldName, 0) + 1);
|
||
}
|
||
stats.put("blocksPerWorld", blocksPerWorld);
|
||
|
||
return stats;
|
||
}
|
||
} |