Files
PlayerBlockLife/src/main/java/com/playerblocklife/PlayerBlockManager.java

652 lines
21 KiB
Java
Raw Normal View History

2026-02-13 18:50:05 +08:00
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;
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");
}
/**
* 为玩家设置生命方块兼容旧方法
2026-02-13 18:50:05 +08:00
*/
public boolean setLifeBlocks(Player player, Location center) {
ConfigManager config = plugin.getConfigManager();
int blockAmount = config.getBlocksPerPlayer();
int spreadRange = config.getSpreadRange();
boolean requireOpenSky = config.isRequireOpenSky();
int maxAttempts = config.getMaxAttempts();
return generateLifeBlocksForPlayer(player, blockAmount, spreadRange, requireOpenSky, maxAttempts);
}
/**
* 为玩家生成生命方块新方法支持自动生成
*/
public boolean generateLifeBlocksForPlayer(Player player, int blockAmount, int spreadRange, boolean requireOpenSky, int maxAttempts) {
2026-02-13 18:50:05 +08:00
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;
2026-02-13 18:50:05 +08:00
// 尝试生成指定数量的方块
while (blocksPlaced < blockAmount && attempts < maxAttempts) {
Location blockLoc = findSurfaceLocation(player.getLocation(), spreadRange, requireOpenSky);
attempts++;
2026-02-13 18:50:05 +08:00
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, boolean requireOpenSky) {
for (int i = 0; i < 10; i++) {
int x = random.nextInt(spreadRange * 2 + 1) - spreadRange;
int z = random.nextInt(spreadRange * 2 + 1) - spreadRange;
// 从中心点上方开始向下寻找地表
Location testLoc = center.clone().add(x, 10, z);
World world = testLoc.getWorld();
if (world == null) continue;
// 向下寻找第一个非空气方块
Block groundBlock = null;
for (int y = 10; y > world.getMinHeight(); y--) {
testLoc.setY(y);
Block block = testLoc.getBlock();
if (!block.getType().isAir()) {
groundBlock = block;
break;
}
}
if (groundBlock == null) continue;
// 检查地表方块上方位置
Location surfaceLoc = groundBlock.getLocation().add(0, 1, 0);
Block surfaceBlock = surfaceLoc.getBlock();
// 检查是否已有方块
if (blockOwners.containsKey(surfaceLoc)) {
continue;
}
// 检查地表方块是否合适
if (!isSuitableLocation(surfaceLoc)) {
continue;
}
// 如果需要上方无方块覆盖,检查上方
if (requireOpenSky) {
boolean hasCover = false;
for (int y = 1; y <= 5; y++) {
Block aboveBlock = surfaceLoc.clone().add(0, y, 0).getBlock();
if (!aboveBlock.getType().isAir()) {
hasCover = true;
break;
}
}
if (hasCover) continue;
}
return surfaceLoc;
}
return null;
}
2026-02-13 18:50:05 +08:00
/**
* 寻找合适的位置
*/
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 placePlayerHead(Location location, UUID playerId, String playerName) {
try {
Block block = location.getBlock();
// 检查方块是否已被占用
if (blockOwners.containsKey(location)) {
return false;
}
// 设置方块为玩家头颅
block.setType(Material.PLAYER_HEAD);
// 获取并设置头颅数据
Skull skullState = (Skull) block.getState();
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);
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 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;
}
}