2.2.0-1.20.4

将生命方块由玩家头换为其他原版多颜色方块
This commit is contained in:
xiaobai
2026-02-16 17:43:39 +08:00
parent 3bfa81f94f
commit ad5cdf1c64
136 changed files with 9850 additions and 686 deletions

View File

@@ -1,6 +1,7 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -8,77 +9,72 @@ import org.bukkit.entity.Player;
import java.util.UUID;
/**
* AdminCommands - PlayerBlockLife游戏模式的管理员命令处理器
*
* <p>处理插件的传统管理员命令适配新的PlayerBlockLife游戏模式<br>
* <ul>
* <li>/pblreload - 重载插件配置</li>
* <li>/pbldelete - 删除指定玩家的生命方块</li>
* <li>/pblrevive - 复活被淘汰的玩家</li>
* <li>/pblstats - 显示PlayerBlockLife游戏统计信息</li>
* </ul>
*
* <p>这些命令在新模式下会与GameStateManager交互以正确管理游戏状态。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class AdminCommands implements CommandExecutor {
private final PlayerBlockLife plugin;
/**
* 构造一个新的管理员命令执行器
*
* @param plugin 插件主类实例
*/
public AdminCommands(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
MessageManager msgManager = plugin.getMessageManager();
ConfigManager config = plugin.getConfigManager();
String commandName = command.getName().toLowerCase();
// 检查命令是否启用
if (!config.isCommandEnabled(commandName)) {
String message = msgManager.getMessage("game.errors.command_disabled",
"&c此命令已被禁用");
sender.sendMessage(message);
return true;
}
// 检查管理员权限和配置
if (!sender.hasPermission("playerblocklife.admin") || config.isAdminOnly(commandName)) {
String message = msgManager.getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
sender.sendMessage(message);
if (!sender.hasPermission("playerblocklife.admin")) {
sender.sendMessage(ChatColor.RED + "你没有权限使用此命令!");
return true;
}
if (commandName.equals("pblreload")) {
// 调用插件的完整重载方法
// 重载配置
plugin.reloadPluginConfig();
String message = msgManager.getCommandMessage("pblreload", "success",
"&a配置已重载");
sender.sendMessage(message);
sender.sendMessage(ChatColor.GREEN + "配置已重载!");
return true;
}
if (commandName.equals("pbldelete")) {
if (args.length < 1) {
String usage = msgManager.getCommandMessage("pbldelete", "usage",
"&c用法: /pbldelete <玩家>");
sender.sendMessage(usage);
sender.sendMessage(ChatColor.RED + "用法: /pbldelete <玩家>");
return true;
}
String targetName = args[0];
Player target = Bukkit.getPlayer(targetName);
UUID targetId;
if (target != null) {
targetId = target.getUniqueId();
} else {
// 尝试从离线玩家获取UUID
try {
targetId = Bukkit.getOfflinePlayer(targetName).getUniqueId();
} catch (Exception e) {
String message = msgManager.getMessage("game.errors.player_not_found",
"&c找不到玩家: {player}");
message = message.replace("{player}", targetName);
sender.sendMessage(message);
return true;
}
if (target == null) {
sender.sendMessage(ChatColor.RED + "找不到玩家: " + targetName);
return true;
}
plugin.getBlockManager().clearPlayerBlocks(targetId);
String message = msgManager.getCommandMessage("pbldelete", "success",
"&a已删除玩家 {player} 的生命方块!");
message = message.replace("{player}", targetName);
sender.sendMessage(message);
// 在新模式下我们使用GameStateManager来清除玩家方块
// 清除玩家的方块数据
UUID targetId = target.getUniqueId();
plugin.getGameStateManager().clearPlayerBlocks(targetId);
sender.sendMessage(ChatColor.GREEN + "已删除玩家 " + targetName + " 的生命方块!");
target.sendMessage(ChatColor.YELLOW + "你的生命方块已被管理员清除。");
return true;
}
@@ -87,87 +83,98 @@ public class AdminCommands implements CommandExecutor {
if (args.length < 1) {
if (!(sender instanceof Player)) {
String usage = msgManager.getCommandMessage("pblrevive", "usage",
"&c用法: /pblrevive [玩家]");
sender.sendMessage(usage);
sender.sendMessage(ChatColor.RED + "用法: /pblrevive <玩家>");
return true;
}
target = (Player) sender;
} else {
target = Bukkit.getPlayer(args[0]);
if (target == null) {
String message = msgManager.getMessage("game.errors.player_offline",
"&c玩家 {player} 不在线!");
message = message.replace("{player}", args[0]);
sender.sendMessage(message);
sender.sendMessage(ChatColor.RED + "玩家 " + args[0] + " 不在线!");
return true;
}
}
if (plugin.getLifeSystem() != null) {
plugin.getLifeSystem().revivePlayer(target);
String message = msgManager.getCommandMessage("pblrevive", "success",
"&a玩家 {player} 已复活!");
message = message.replace("{player}", target.getName());
sender.sendMessage(message);
// 在新模式下,复活玩家需要将其从淘汰列表中移除并恢复生存模式
UUID targetId = target.getUniqueId();
if (plugin.getGameStateManager().getRemainingBlocks(targetId) <= 0) {
// 如果玩家已被淘汰,需要将其重新添加到存活玩家列表
java.util.List<UUID> alivePlayers = plugin.getGameStateManager().getAlivePlayers();
if (!alivePlayers.contains(targetId)) {
// 在新模式下,复活需要重新生成方块或将其重新加入游戏
sender.sendMessage(ChatColor.RED + "在新模式下,复活功能需要重新开始游戏或为玩家重新生成方块。");
sender.sendMessage(ChatColor.YELLOW + "建议使用 /pbl rstgm 重置游戏。");
} else {
target.setGameMode(org.bukkit.GameMode.SURVIVAL);
target.sendMessage(ChatColor.GREEN + "你已被复活!");
sender.sendMessage(ChatColor.GREEN + "玩家 " + target.getName() + " 已被复活!");
}
} else {
sender.sendMessage("§c复活失败生命系统未初始化");
target.setGameMode(org.bukkit.GameMode.SURVIVAL);
target.sendMessage(ChatColor.GREEN + "你已被复活!");
sender.sendMessage(ChatColor.GREEN + "玩家 " + target.getName() + " 已被复活!");
}
return true;
}
if (commandName.equals("pblstats")) {
if (plugin.getBlockManager() == null) {
sender.sendMessage("§c方块管理器未初始化");
return true;
}
int totalPlayers = plugin.getBlockManager().getPlayerBlocksCount();
int totalBlocks = plugin.getBlockManager().getTotalBlocksCount();
// 显示新模式下的游戏统计
GameStateManager gameStateManager = plugin.getGameStateManager();
GameStateManager.GameState currentState = gameStateManager.getCurrentState();
// 获取统计标题
String title = msgManager.getCommandMessage("pblstats", "title",
"&6=== PlayerBlockLife 统计 ===");
sender.sendMessage(title);
sender.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 游戏统计 ===");
sender.sendMessage(ChatColor.AQUA + "游戏状态: " + getStateText(currentState));
// 在线玩家统计
String onlineMsg = msgManager.getCommandMessage("pblstats", "online_players",
"&e在线玩家: {count}");
onlineMsg = onlineMsg.replace("{count}", String.valueOf(Bukkit.getOnlinePlayers().size()));
sender.sendMessage(onlineMsg);
int onlineCount = Bukkit.getOnlinePlayers().size();
int opCount = 0;
int participantCount = 0;
// 总方块统计
String blocksMsg = msgManager.getCommandMessage("pblstats", "total_blocks",
"&e总生命方块: {count}");
blocksMsg = blocksMsg.replace("{count}", String.valueOf(totalBlocks));
sender.sendMessage(blocksMsg);
// 淘汰玩家统计
int eliminatedCount = 0;
for (Player player : Bukkit.getOnlinePlayers()) {
if (plugin.getBlockManager().getRemainingBlocks(player.getUniqueId()) == 0) {
eliminatedCount++;
if (player.isOp()) {
opCount++;
} else {
participantCount++;
}
}
String eliminatedMsg = msgManager.getCommandMessage("pblstats", "eliminated_players",
"&e已淘汰玩家: {count}");
eliminatedMsg = eliminatedMsg.replace("{count}", String.valueOf(eliminatedCount));
sender.sendMessage(eliminatedMsg);
sender.sendMessage("§7在线玩家详情:");
sender.sendMessage(ChatColor.YELLOW + "在线玩家: " + onlineCount +
" (参与者: " + participantCount + ", OP: " + opCount + ")");
if (currentState == GameStateManager.GameState.STARTED) {
int aliveCount = gameStateManager.getAlivePlayersCount();
sender.sendMessage(ChatColor.GREEN + "存活玩家: " + aliveCount);
if (gameStateManager.isLimitedTime()) {
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - gameStateManager.getGameStartTime()) / (1000 * 60);
long remainingMinutes = Math.max(0, gameStateManager.getGameDuration() - elapsedMinutes);
sender.sendMessage(ChatColor.YELLOW + "剩余时间: " + remainingMinutes + " 分钟");
}
}
sender.sendMessage(ChatColor.GRAY + "-------------------");
for (Player player : Bukkit.getOnlinePlayers()) {
int blocks = plugin.getBlockManager().getRemainingBlocks(player.getUniqueId());
String status = blocks > 0 ? "§a存活" : "§c已淘汰";
sender.sendMessage("§7- " + player.getName() + ": §e" + blocks + " §7/ §a5 §7(" + status + "§7)");
int blocks = gameStateManager.getRemainingBlocks(player.getUniqueId());
String status = player.isOp() ? ChatColor.GOLD + "OP" :
(blocks > 0 ? ChatColor.GREEN + "存活" : ChatColor.RED + "淘汰");
sender.sendMessage(ChatColor.WHITE + "- " + player.getName() + ": " +
ChatColor.YELLOW + blocks + "/5 " +
ChatColor.GRAY + "(" + status + ChatColor.GRAY + ")");
}
sender.sendMessage("§a=================================");
return true;
}
String unknownMsg = msgManager.getMessage("game.errors.invalid_arguments",
"&c未知的管理员命令");
sender.sendMessage(unknownMsg);
sender.sendMessage(ChatColor.RED + "未知的管理员命令!");
return true;
}
private String getStateText(GameStateManager.GameState state) {
switch (state) {
case WAITING: return ChatColor.YELLOW + "等待中";
case STARTED: return ChatColor.GREEN + "进行中";
case FINISHED: return ChatColor.RED + "已结束";
default: return ChatColor.GRAY + "未知";
}
}
}

View File

@@ -16,114 +16,159 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* BlockBreakListener - PlayerBlockLife游戏模式下的方块破坏和放置监听器
*
* <p>监听玩家对生命方块的破坏行为并在PlayerBlockLife游戏模式下进行相应处理
* <ul>
* <li>检查被破坏的方块是否为生命方块(羊毛、玻璃、水泥)</li>
* <li>验证破坏者是否有权限破坏该方块</li>
* <li>更新玩家剩余生命方块数量</li>
* <li>处理玩家淘汰逻辑</li>
* <li>在游戏未开始时阻止方块破坏</li>
* </ul>
*
* <p>此监听器与GameStateManager协作确保游戏规则正确执行。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class BlockBreakListener implements Listener {
private final PlayerBlockLife plugin;
/**
* 构造一个新的方块破坏监听器
*
* @param plugin 插件主类实例
*/
public BlockBreakListener(PlayerBlockLife plugin) {
this.plugin = plugin;
}
/**
* 处理方块被破坏的事件
*
* @param event 方块破坏事件
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
Player breaker = event.getPlayer();
Block block = event.getBlock();
Location location = block.getLocation();
if (block.getType() != Material.PLAYER_HEAD &&
block.getType() != Material.PLAYER_WALL_HEAD) {
// 检查是否为生命方块(羊毛、玻璃或水泥块)
if (!isLifeBlockMaterial(block.getType())) {
return;
}
UUID ownerId = plugin.getBlockManager().getBlockOwner(location);
if (ownerId == null) {
// 检查游戏是否已开始
if (plugin.getGameStateManager().getCurrentState() != GameStateManager.GameState.STARTED) {
event.setCancelled(true);
breaker.sendMessage(org.bukkit.ChatColor.RED + "游戏尚未开始,无法破坏方块!");
return;
}
// 检查是否为玩家的生命方块
UUID ownerId = getBlockOwner(location);
if (ownerId == null) {
return; // 不是生命方块
}
if (ownerId.equals(breaker.getUniqueId())) {
if (breaker.getGameMode() != GameMode.CREATIVE) {
String message = plugin.getMessageManager().getMessage("game.errors.cannot_break_own_block",
"&c你不能挖掘自己的生命方块");
breaker.sendMessage(message);
breaker.sendMessage(org.bukkit.ChatColor.RED + "你不能挖掘自己的生命方块!");
event.setCancelled(true);
}
return;
}
if (plugin.getBlockManager().removeBlock(location, breaker)) {
int remaining = plugin.getBlockManager().getRemainingBlocks(ownerId);
if (plugin.getGameStateManager().removeBlock(location, breaker)) {
int remaining = plugin.getGameStateManager().getRemainingBlocks(ownerId);
Player owner = Bukkit.getPlayer(ownerId);
String ownerName = owner != null ? owner.getName() : Bukkit.getOfflinePlayer(ownerId).getName();
if (remaining <= 0) {
plugin.getLifeSystem().handlePlayerDeath(ownerId);
}
// 通知破坏者
Map<String, String> variables = new HashMap<>();
variables.put("owner", ownerName != null ? ownerName : "未知玩家");
variables.put("remaining", String.valueOf(remaining));
variables.put("total", String.valueOf(plugin.getConfigManager().getBlocksPerPlayer()));
variables.put("total", String.valueOf(5)); // 使用固定值5因为配置已移除
String breakerMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.breaker",
"&a你破坏了 {owner} 的生命方块!", variables);
breaker.sendMessage(breakerMsg);
String remainingMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.remaining",
"&7剩余方块: {remaining}/{total}", variables);
breaker.sendMessage(remainingMsg);
breaker.sendMessage(org.bukkit.ChatColor.GREEN + "你破坏了 " + org.bukkit.ChatColor.YELLOW +
(ownerName != null ? ownerName : "未知玩家") + org.bukkit.ChatColor.GREEN + " 的生命方块!");
breaker.sendMessage(org.bukkit.ChatColor.GRAY + "剩余方块: " + org.bukkit.ChatColor.YELLOW +
remaining + org.bukkit.ChatColor.GRAY + "/" + org.bukkit.ChatColor.RED + 5);
if (remaining == 1) {
String lastBlockMsg = plugin.getMessageManager().getMessage("game.block.last_block_warning",
"&6⚡ 对方只剩最后1个生命方块了");
breaker.sendMessage(lastBlockMsg);
breaker.sendMessage(org.bukkit.ChatColor.GOLD + "⚡ 对方只剩最后1个生命方块了");
breaker.playSound(breaker.getLocation(),
org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.5f);
}
// 通知方块所有者
if (owner != null && owner.isOnline()) {
variables.put("breaker", breaker.getName());
String ownerMsg = plugin.getMessageManager().getFormattedMessage("game.block.destroyed.owner",
"&c⚠ 警告!你的生命方块被 {breaker} 破坏了!剩余: {remaining}/{total}", variables);
owner.sendMessage(ownerMsg);
owner.sendMessage(org.bukkit.ChatColor.RED + "⚠ 警告!你的生命方块被 " +
org.bukkit.ChatColor.YELLOW + breaker.getName() + org.bukkit.ChatColor.RED + " 破坏了!");
owner.sendMessage(org.bukkit.ChatColor.GRAY + "剩余方块: " + org.bukkit.ChatColor.YELLOW +
remaining + org.bukkit.ChatColor.GRAY + "/" + org.bukkit.ChatColor.RED + 5);
if (remaining == 0) {
String allDestroyedMsg = plugin.getMessageManager().getMessage("game.block.all_destroyed",
"&c☠ 你的所有生命方块已被破坏!你已被淘汰!");
owner.sendMessage(allDestroyedMsg);
owner.sendMessage(org.bukkit.ChatColor.DARK_RED + "☠ 你的所有生命方块已被破坏!你已被淘汰!");
}
}
// 广播(如果启用)
if (plugin.getConfigManager().isBroadcastOnBlockBreak()) {
String broadcastMsg = plugin.getMessageManager().getFormattedMessage("broadcast.block_destroyed",
"&6{breaker} &7破坏了 &c{owner} &7的生命方块", variables);
int range = plugin.getConfigManager().getBroadcastRange();
for (Player nearby : breaker.getWorld().getPlayers()) {
if (nearby.getLocation().distance(breaker.getLocation()) <= range &&
nearby != breaker && (owner == null || nearby != owner)) {
nearby.sendMessage(broadcastMsg);
nearby.sendMessage(org.bukkit.ChatColor.GOLD + breaker.getName() +
org.bukkit.ChatColor.GRAY + " 破坏了 " + org.bukkit.ChatColor.YELLOW +
(ownerName != null ? ownerName : "某人") + org.bukkit.ChatColor.GRAY + " 的生命方块!");
}
}
}
}
}
/**
* 处理方块被放置的事件
*
* @param event 方块放置事件
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
Player player = event.getPlayer();
Block block = event.getBlock();
if (block.getType() == Material.PLAYER_HEAD ||
block.getType() == Material.PLAYER_WALL_HEAD) {
UUID ownerId = plugin.getBlockManager().getBlockOwner(block.getLocation());
if (ownerId != null && !ownerId.equals(player.getUniqueId())) {
String message = plugin.getMessageManager().getMessage("game.errors.cannot_place_in_block_area",
"&c你不能在这里放置方块这是别人的生命方块区域");
player.sendMessage(message);
event.setCancelled(true);
}
// 检查是否为生命方块材料
if (isLifeBlockMaterial(block.getType())) {
player.sendMessage(org.bukkit.ChatColor.RED + "你不能放置生命方块!");
event.setCancelled(true);
return;
}
}
/**
* 检查材料是否为生命方块材料
*/
private boolean isLifeBlockMaterial(Material material) {
return material.name().endsWith("_WOOL") ||
material.name().endsWith("_STAINED_GLASS") ||
material.name().endsWith("_CONCRETE");
}
/**
* 获取方块所有者
*/
private UUID getBlockOwner(Location location) {
// 检查是否有任何玩家的方块在此位置
for (Map.Entry<UUID, java.util.List<Location>> entry : plugin.getGameStateManager().getPlayerBlocks().entrySet()) {
if (entry.getValue().contains(location)) {
return entry.getKey();
}
}
return null;
}
}

View File

@@ -1,101 +1,94 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.UUID;
/**
* CheckLifeBlocksCommand - 显示玩家在PlayerBlockLife游戏模式下的生命方块状态
*
* <p>在PlayerBlockLife游戏模式下此命令显示玩家当前的剩余生命方块数、游戏状态和分配的颜色。
* 与旧模式不同,此命令不再显示生命方块的具体位置,而是提供当前游戏状态信息。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class CheckLifeBlocksCommand implements CommandExecutor {
private final PlayerBlockLife plugin;
/**
* 构造一个新的检查生命方块命令执行器
*
* @param plugin 插件主类实例
*/
public CheckLifeBlocksCommand(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// 检查命令是否启用
if (!plugin.getConfigManager().isCommandEnabled("checklifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.command_disabled",
"&c此命令已被禁用");
sender.sendMessage(message);
return true;
}
if (!(sender instanceof Player)) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c只有玩家可以使用此命令");
sender.sendMessage(message);
sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!控制台无法查看具体玩家的生命方块状态。");
return true;
}
Player player = (Player) sender;
UUID playerId = player.getUniqueId();
// 获取当前玩家在新模式下的剩余方块数
int remainingBlocks = plugin.getGameStateManager().getRemainingBlocks(player.getUniqueId());
// 检查是否允许玩家自己使用
if (!plugin.getConfigManager().isSelfUseAllowed("checklifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
player.sendMessage(message);
return true;
}
List<Location> blocks = plugin.getBlockManager().getPlayerBlocks(playerId);
int remaining = blocks.size();
MessageManager msgManager = plugin.getMessageManager();
if (remaining == 0) {
String noBlocksMsg = msgManager.getCommandMessage("checklifeblocks", "no_blocks",
"&c你还没有生命方块");
player.sendMessage(noBlocksMsg);
return true;
}
// 获取消息
String successMsg = msgManager.getCommandMessage("checklifeblocks", "success",
"&e你的生命方块位置");
player.sendMessage(successMsg);
player.sendMessage(ChatColor.AQUA + "=== 你的生命方块状态 ===");
player.sendMessage(ChatColor.GRAY + "剩余方块数量: " + ChatColor.YELLOW + remainingBlocks +
ChatColor.GRAY + " / " + ChatColor.RED + 5);
player.sendMessage("§7剩余方块数量: §e" + remaining + " §7/ §a5");
// 根据游戏状态显示不同信息
GameStateManager.GameState state = plugin.getGameStateManager().getCurrentState();
switch (state) {
case WAITING:
player.sendMessage(ChatColor.YELLOW + "游戏状态: 等待开始");
break;
case STARTED:
player.sendMessage(ChatColor.GREEN + "游戏状态: 进行中");
break;
case FINISHED:
player.sendMessage(ChatColor.RED + "游戏状态: 已结束");
break;
}
// 显示生命值(如果启用)
if (plugin.getConfigManager().isHealthSystemEnabled()) {
Integer health = plugin.getLifeSystem().getPlayerHealth(playerId);
player.sendMessage("§7当前生命值: §c" + (health != null ? health : "20") + "");
// 显示玩家颜色
org.bukkit.Material playerColor = plugin.getGameStateManager().getPlayerColor(player.getUniqueId());
if (playerColor != null) {
String colorName = getColorName(playerColor);
player.sendMessage(ChatColor.GRAY + "你的方块颜色: " + ChatColor.AQUA + colorName);
}
if (remaining <= 2) {
String warningMsg = msgManager.getMessage("game.block.warning_low_blocks",
"&c⚠ 警告!生命方块即将耗尽!");
player.sendMessage(warningMsg);
}
player.sendMessage("§7方块位置:");
for (int i = 0; i < blocks.size(); i++) {
Location loc = blocks.get(i);
String worldName = loc.getWorld() != null ? loc.getWorld().getName() : "未知世界";
String locationMsg = msgManager.getMessage("game.block.location_item",
"&7- {world} ({x}, {y}, {z})");
locationMsg = locationMsg.replace("{world}", worldName)
.replace("{x}", String.valueOf(loc.getBlockX()))
.replace("{y}", String.valueOf(loc.getBlockY()))
.replace("{z}", String.valueOf(loc.getBlockZ()));
player.sendMessage(locationMsg);
}
if (remaining > 0) {
Location nearestBlock = plugin.getBlockManager().getNearestBlock(player);
if (nearestBlock != null) {
double distance = player.getLocation().distance(nearestBlock);
player.sendMessage("§7最近方块距离: §a" + (int)distance + " §7格");
}
}
return true;
}
/**
* 获取材料颜色名称
*/
private String getColorName(org.bukkit.Material material) {
String name = material.name();
if (name.contains("WHITE")) return "白色";
if (name.contains("ORANGE")) return "橙色";
if (name.contains("MAGENTA")) return "品红色";
if (name.contains("LIGHT_BLUE")) return "淡蓝色";
if (name.contains("YELLOW")) return "黄色";
if (name.contains("LIME")) return "黄绿色";
if (name.contains("PINK")) return "粉色";
if (name.contains("GRAY")) return "灰色";
if (name.contains("LIGHT_GRAY")) return "淡灰色";
if (name.contains("CYAN")) return "青色";
if (name.contains("PURPLE")) return "紫色";
if (name.contains("BLUE")) return "蓝色";
if (name.contains("BROWN")) return "棕色";
if (name.contains("GREEN")) return "绿色";
if (name.contains("RED")) return "红色";
if (name.contains("BLACK")) return "黑色";
return "未知";
}
}

View File

@@ -22,17 +22,16 @@ import java.nio.charset.StandardCharsets;
* <li>支持多种皮肤来源的优先级配置</li>
* </ul>
*
* <p><b>SkinsRestorer配置支持</b>
* <p><b>SkinsRestorer配置支持</b></p>
* <ul>
* <li><code>skin.source</code>皮肤来源优先级skinsrestorer/player_profile/local_cache</li>
* <li><code>skin.use-skinsrestorer</code>是否启用SkinsRestorer支持</li>
* <li><code>skin.cache.expire_days</code>:皮肤缓存过期时间</li>
* <li>默认配置已优化优先使用SkinsRestorer以支持离线服务器</li>
* </ul>
* </p>
*
* @author xiaobai
* @version 2.1.0
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class ConfigManager {
@@ -126,14 +125,6 @@ public class ConfigManager {
*/
private void updateConfig(int fromVersion, int toVersion) {
if (fromVersion == 1 && toVersion == 2) {
// 添加自动生成配置
if (!config.contains("auto-generation.enabled")) {
config.set("auto-generation.enabled", true);
config.set("auto-generation.require_open_sky", true);
config.set("auto-generation.max_attempts", 50);
config.set("auto-generation.on_failure", "notify");
}
// 添加命令启用配置
if (!config.contains("commands.setlifeblocks.enabled")) {
config.set("commands.setlifeblocks.enabled", true);
@@ -206,195 +197,201 @@ public class ConfigManager {
return config;
}
// 以下为配置项的获取方法
public int getBlocksPerPlayer() {
return getConfig().getInt("blocks.amount", 5);
}
public int getSpreadRange() {
return getConfig().getInt("blocks.spread", 5);
}
public int getMinDistance() {
return getConfig().getInt("blocks.min-distance", 10);
}
public int getDepth() {
return getConfig().getInt("blocks.depth", -1);
}
public String getBlockMaterial() {
return getConfig().getString("blocks.material", "player_head");
}
/**
* 检查玩家方块被挖光时是否死亡
*
* @return 如果玩家方块被挖光时死亡则返回true否则返回false
*/
public boolean isDieWhenBlocksGone() {
return getConfig().getBoolean("game.die_when_blocks_gone", true);
}
/**
* 检查玩家死亡后是否成为观察者
*
* @return 如果玩家死亡后成为观察者则返回true否则返回false
*/
public boolean isBecomeSpectator() {
return getConfig().getBoolean("game.become_spectator", true);
}
/**
* 检查是否启用生命值系统
*
* @return 如果启用生命值系统则返回true否则返回false
*/
public boolean isHealthSystemEnabled() {
return getConfig().getBoolean("game.health_system", true);
}
public boolean isSkinSystemEnabled() {
return getConfig().getBoolean("skin.enabled", true);
}
/**
* 获取皮肤来源配置
* 检查是否启用自动保存
*
* <p>支持的皮肤来源:
* <ul>
* <li><b>skinsrestorer</b>优先从SkinsRestorer插件获取皮肤纹理数据
* <ul>
* <li>推荐用于离线服务器</li>
* <li>支持玩家自定义皮肤</li>
* <li>避免默认Steve皮肤问题</li>
* </ul>
* </li>
* <li><b>player_profile</b>优先使用Bukkit的PlayerProfile API
* <ul>
* <li>需要玩家在线验证</li>
* <li>适合在线服务器</li>
* <li>支持Mojang官方皮肤</li>
* </ul>
* </li>
* <li><b>local_cache</b>:优先从本地缓存加载皮肤数据
* <ul>
* <li>减少网络请求</li>
* <li>提高加载速度</li>
* <li>支持离线使用</li>
* </ul>
* </li>
* </ul>
* </p>
*
* <p>默认配置已将此值设为"skinsrestorer",以优化离线服务器体验。</p>
*
* @return 皮肤来源配置值
* @see #useSkinsRestorer()
* @see SkinManager#loadPlayerSkinAsync()
* @return 如果启用自动保存则返回true否则返回false
*/
public String getSkinSource() {
return getConfig().getString("skin.source", "skinsrestorer");
}
/**
* 检查是否启用SkinsRestorer插件支持
*
* <p>当此方法返回true时插件将
* <ul>
* <li>优先从SkinsRestorer插件获取玩家皮肤纹理</li>
* <li>支持离线服务器获取玩家自定义皮肤</li>
* <li>避免方块总是显示默认Steve皮肤的问题</li>
* <li>使用反射安全调用SkinsRestorer API无需硬依赖</li>
* </ul>
* </p>
*
* <p>默认配置已将此值设为true以优化离线服务器体验。</p>
*
* @return 如果启用SkinsRestorer支持返回true否则返回false
* @see #getSkinSource()
*/
public boolean useSkinsRestorer() {
return getConfig().getBoolean("skin.use-skinsrestorer", true);
}
public int getCacheExpireDays() {
return getConfig().getInt("skin.cache.expire_days", 7);
}
public boolean isAutoSaveEnabled() {
return getConfig().getBoolean("storage.auto_save.enabled", true);
}
/**
* 获取自动保存间隔
*
* @return 自动保存间隔默认为300秒5分钟
*/
public int getAutoSaveInterval() {
return getConfig().getInt("storage.auto_save.interval", 300);
}
/**
* 获取存储类型配置
*
* @return 存储类型配置值,默认为"yaml"
*/
public String getStorageType() {
return getConfig().getString("storage.type", "yaml");
}
/**
* 检查是否在方块被破坏时广播消息
*
* @return 如果在方块被破坏时广播消息则返回true否则返回false
*/
public boolean isBroadcastOnBlockBreak() {
return getConfig().getBoolean("game.broadcast.on_block_break", true);
}
/**
* 检查是否在玩家死亡时广播消息
*
* @return 如果在玩家死亡时广播消息则返回true否则返回false
*/
public boolean isBroadcastOnPlayerDeath() {
return getConfig().getBoolean("game.broadcast.on_player_death", true);
}
/**
* 获取广播范围
*
* @return 广播范围方块默认为30
*/
public int getBroadcastRange() {
return getConfig().getInt("game.broadcast.range", 30);
}
/**
* 检查是否给予挖掘奖励经验
*
* @return 如果给予挖掘奖励经验则返回true否则返回false
*/
public boolean isGiveExpReward() {
return getConfig().getBoolean("game.break_rewards.give_exp", true);
}
/**
* 获取奖励经验数量
*
* @return 奖励经验数量默认为5
*/
public int getExpRewardAmount() {
return getConfig().getInt("game.break_rewards.exp_amount", 5);
}
/**
* 检查是否保护方块免受爆炸破坏
*
* @return 如果保护方块免受爆炸破坏则返回true否则返回false
*/
public boolean isProtectFromExplosions() {
return getConfig().getBoolean("protection.protect_from_explosions", true);
}
/**
* 检查是否保护方块免受火灾破坏
*
* @return 如果保护方块免受火灾破坏则返回true否则返回false
*/
public boolean isProtectFromFire() {
return getConfig().getBoolean("protection.protect_from_fire", true);
}
/**
* 检查是否保护方块免受活塞推动
*
* @return 如果保护方块免受活塞推动则返回true否则返回false
*/
public boolean isProtectFromPistons() {
return getConfig().getBoolean("protection.protect_from_pistons", true);
}
// 自动生成配置获取方法
public boolean isAutoGenerationEnabled() {
return getConfig().getBoolean("auto-generation.enabled", true);
}
public boolean isRequireOpenSky() {
return getConfig().getBoolean("auto-generation.require_open_sky", true);
}
public int getMaxAttempts() {
return getConfig().getInt("auto-generation.max_attempts", 50);
}
public String getOnFailureAction() {
return getConfig().getString("auto-generation.on_failure", "notify");
}
// 命令启用配置获取方法
/**
* 检查命令是否启用
*
* @param commandName 命令名称
* @return 如果命令启用则返回true否则返回false
*/
public boolean isCommandEnabled(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".enabled", true);
}
/**
* 检查是否允许玩家自己使用命令
*
* @param commandName 命令名称
* @return 如果允许玩家自己使用命令则返回true否则返回false
*/
public boolean isSelfUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_self_use", true);
}
/**
* 检查是否允许管理员使用命令
*
* @param commandName 命令名称
* @return 如果允许管理员使用命令则返回true否则返回false
*/
public boolean isAdminUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_admin_use", true);
}
/**
* 检查命令是否仅管理员可用
*
* @param commandName 命令名称
* @return 如果命令仅管理员可用则返回true否则返回false
*/
public boolean isAdminOnly(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".admin_only", false);
}
// 消息文件配置获取方法
/**
* 检查是否使用外部消息文件
*
* @return 如果使用外部消息文件则返回true否则返回false
*/
public boolean useExternalMessageFile() {
return getConfig().getBoolean("messages.use_external_file", true);
}
/**
* 获取外部消息文件名
*
* @return 外部消息文件名,默认为"messages.yml"
*/
public String getExternalMessageFileName() {
return getConfig().getString("messages.external_file", "messages.yml");
}
/**
* 获取消息
*
* @param path 消息路径
* @param defaultValue 默认值
* @return 消息内容
*/
public String getMessage(String path, String defaultValue) {
// 优先从外部消息文件获取
if (useExternalMessageFile()) {

View File

@@ -0,0 +1,883 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.ScoreboardManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
/**
* 游戏状态管理器 - PlayerBlockLife生存游戏模式的核心管理器
*
* <p>主要功能包括:
* <ul>
* <li>管理游戏状态(等待中/游戏中/已结束)</li>
* <li>为非OP玩家分配独特颜色的生命方块</li>
* <li>处理游戏开始时的生命方块生成</li>
* <li>控制游戏开始和结束逻辑</li>
* <li>管理实时计分板显示(游戏状态、剩余方块、限时等)</li>
* <li>处理游戏重置和玩家淘汰</li>
* <li>监控游戏结束条件(仅剩一名存活玩家或限时结束)</li>
* </ul>
*
* <p><b>游戏模式特性:</b>
* <ul>
* <li>OP玩家始终处于观察者模式不参与游戏</li>
* <li>非OP玩家在等待时处于冒险模式手中持有对应颜色方块</li>
* <li>游戏开始后非OP玩家转为生存模式并清空背包</li>
* <li>使用不同颜色的羊毛、玻璃、水泥方块作为生命方块</li>
* <li>支持限时游戏模式</li>
* <li>游戏结束时显示胜利玩家</li>
* </ul>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 4.0.0
*/
public class GameStateManager {
public enum GameState {
WAITING, // 等待开始
STARTED, // 游戏进行中
FINISHED // 游戏结束
}
private final PlayerBlockLife plugin;
private GameState currentState = GameState.WAITING;
private final Map<UUID, Material> playerColors = new HashMap<>(); // 玩家颜色分配
private final Map<UUID, List<Location>> playerBlocks = new HashMap<>(); // 玩家方块位置
private final List<UUID> alivePlayers = new ArrayList<>(); // 存活玩家
private int gameDuration = 0; // 限时游戏持续时间(分钟)
private long gameStartTime = 0; // 游戏开始时间戳
private boolean isLimitedTime = false; // 是否为限时游戏
private final ScoreboardManager scoreboardManager;
private Scoreboard gameScoreboard;
private Objective gameObjective;
// 可用的羊毛、玻璃、水泥颜色
private static final Material[] WOOL_COLORS = {
Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL,
Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL,
Material.LIGHT_GRAY_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL,
Material.BROWN_WOOL, Material.GREEN_WOOL, Material.RED_WOOL, Material.BLACK_WOOL
};
private static final Material[] GLASS_COLORS = {
Material.WHITE_STAINED_GLASS, Material.ORANGE_STAINED_GLASS, Material.MAGENTA_STAINED_GLASS, Material.LIGHT_BLUE_STAINED_GLASS,
Material.YELLOW_STAINED_GLASS, Material.LIME_STAINED_GLASS, Material.PINK_STAINED_GLASS, Material.GRAY_STAINED_GLASS,
Material.LIGHT_GRAY_STAINED_GLASS, Material.CYAN_STAINED_GLASS, Material.PURPLE_STAINED_GLASS, Material.BLUE_STAINED_GLASS,
Material.BROWN_STAINED_GLASS, Material.GREEN_STAINED_GLASS, Material.RED_STAINED_GLASS, Material.BLACK_STAINED_GLASS
};
private static final Material[] CONCRETE_COLORS = {
Material.WHITE_CONCRETE, Material.ORANGE_CONCRETE, Material.MAGENTA_CONCRETE, Material.LIGHT_BLUE_CONCRETE,
Material.YELLOW_CONCRETE, Material.LIME_CONCRETE, Material.PINK_CONCRETE, Material.GRAY_CONCRETE,
Material.LIGHT_GRAY_CONCRETE, Material.CYAN_CONCRETE, Material.PURPLE_CONCRETE, Material.BLUE_CONCRETE,
Material.BROWN_CONCRETE, Material.GREEN_CONCRETE, Material.RED_CONCRETE, Material.BLACK_CONCRETE
};
public GameStateManager(PlayerBlockLife plugin) {
this.plugin = plugin;
this.scoreboardManager = Bukkit.getScoreboardManager();
createGameScoreboard();
}
private void createGameScoreboard() {
this.gameScoreboard = scoreboardManager.getNewScoreboard();
this.gameObjective = gameScoreboard.registerNewObjective("pbl_game", "dummy", ChatColor.GOLD + "PlayerBlockLife 游戏");
this.gameObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
// 初始化计分板内容
updateScoreboard();
}
public GameState getCurrentState() {
return currentState;
}
public void setCurrentState(GameState state) {
this.currentState = state;
updateScoreboard();
// 根据新状态更新玩家
switch (state) {
case WAITING:
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.isOp()) {
player.setGameMode(GameMode.SPECTATOR);
} else {
player.setGameMode(GameMode.ADVENTURE);
player.getInventory().clear();
giveColorBlockToPlayer(player);
}
}
break;
case STARTED:
// 游戏开始后非OP玩家设置为生存模式并清空背包
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.isOp()) {
player.setGameMode(GameMode.SURVIVAL);
player.getInventory().clear();
// 添加玩家到存活列表(如果他们有分配的颜色,意味着他们是参与者)
if (playerColors.containsKey(player.getUniqueId())) {
if (!alivePlayers.contains(player.getUniqueId())) {
alivePlayers.add(player.getUniqueId());
}
}
}
}
gameStartTime = System.currentTimeMillis();
break;
case FINISHED:
// 游戏结束后,所有玩家设置为冒险模式
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.isOp()) {
player.setGameMode(GameMode.SPECTATOR);
} else {
player.setGameMode(GameMode.ADVENTURE);
}
}
break;
}
}
/**
* 分配玩家颜色
*/
/**
* 为玩家分配颜色
*
* @param player 要分配颜色的玩家
*/
public void assignPlayerColor(Player player) {
if (playerColors.containsKey(player.getUniqueId())) {
return; // 已分配颜色
}
// 如果所有颜色都已分配,将玩家设置为观察者模式
if (playerColors.size() >= WOOL_COLORS.length) {
player.setGameMode(GameMode.SPECTATOR);
player.sendMessage(ChatColor.YELLOW + "所有颜色都已分配,您将作为观察者参与游戏。");
return;
}
// 随机分配一个未使用的颜色
List<Material> availableColors = new ArrayList<>();
for (Material wool : WOOL_COLORS) {
boolean isUsed = false;
for (Material usedColor : playerColors.values()) {
if (usedColor == wool) {
isUsed = true;
break;
}
}
if (!isUsed) {
availableColors.add(wool);
}
}
if (!availableColors.isEmpty()) {
Material selectedColor = availableColors.get(new Random().nextInt(availableColors.size()));
playerColors.put(player.getUniqueId(), selectedColor);
// 给玩家对应颜色的方块
giveColorBlockToPlayer(player);
String colorName = getMaterialColorName(selectedColor);
player.sendMessage(ChatColor.GREEN + "您被分配了 " + ChatColor.AQUA + colorName + ChatColor.GREEN + " 颜色的生命方块!");
}
}
/**
* 给玩家对应颜色的方块
*/
private void giveColorBlockToPlayer(Player player) {
Material color = playerColors.get(player.getUniqueId());
if (color != null) {
player.getInventory().clear();
player.getInventory().addItem(new org.bukkit.inventory.ItemStack(color, 1));
}
}
/**
* 获取材料的颜色名称
*/
private String getMaterialColorName(Material material) {
String name = material.name();
if (name.contains("WHITE")) return "白色";
if (name.contains("ORANGE")) return "橙色";
if (name.contains("MAGENTA")) return "品红色";
if (name.contains("LIGHT_BLUE")) return "淡蓝色";
if (name.contains("YELLOW")) return "黄色";
if (name.contains("LIME")) return "黄绿色";
if (name.contains("PINK")) return "粉色";
if (name.contains("GRAY")) return "灰色";
if (name.contains("LIGHT_GRAY")) return "淡灰色";
if (name.contains("CYAN")) return "青色";
if (name.contains("PURPLE")) return "紫色";
if (name.contains("BLUE")) return "蓝色";
if (name.contains("BROWN")) return "棕色";
if (name.contains("GREEN")) return "绿色";
if (name.contains("RED")) return "红色";
if (name.contains("BLACK")) return "黑色";
return "未知";
}
/**
* 为玩家生成生命方块
*/
/**
* 为玩家生成生命方块
*
* @param player 要生成生命方块的玩家
* @return 生成成功返回true否则返回false
*/
public boolean generateLifeBlocksForPlayer(Player player) {
if (!playerColors.containsKey(player.getUniqueId()) || currentState != GameState.STARTED) {
return false;
}
Material color = playerColors.get(player.getUniqueId());
ConfigManager config = plugin.getConfigManager();
int blockAmount = 5; // 使用固定值,因为配置已移除
int spreadRange = 5; // 使用固定值,因为配置已移除
List<Location> blocks = new ArrayList<>();
int blocksPlaced = 0;
int attempts = 0;
Random random = new Random();
// 尝试生成指定数量的方块
while (blocksPlaced < blockAmount && attempts < 50) {
Location center = player.getLocation();
int x = random.nextInt(spreadRange * 2 + 1) - spreadRange;
int z = random.nextInt(spreadRange * 2 + 1) - spreadRange;
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) {
attempts++;
continue;
}
// 地表位置 = 固体方块上方一格
Location surfaceLoc = groundBlock.getLocation().add(0, 1, 0);
// 检查是否已有方块
if (isLifeBlock(surfaceLoc)) {
attempts++;
continue;
}
// 检查位置是否合适
if (!isSuitableLocation(surfaceLoc)) {
attempts++;
continue;
}
// 放置方块
surfaceLoc.getBlock().setType(color);
blocks.add(surfaceLoc);
blocksPlaced++;
// 添加放置效果
spawnPlaceEffects(surfaceLoc);
}
if (blocksPlaced > 0) {
playerBlocks.put(player.getUniqueId(), blocks);
return true;
} else {
return false;
}
}
/**
* 检查位置是否适合放置生命方块
*/
private boolean isSuitableLocation(Location location) {
Block block = location.getBlock();
// 检查是否已有方块
if (isLifeBlock(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;
}
/**
* 生成放置效果
*/
private void spawnPlaceEffects(Location location) {
if (location.getWorld() != null) {
// 粒子效果
location.getWorld().spawnParticle(org.bukkit.Particle.ENCHANTMENT_TABLE,
location.clone().add(0.5, 0.5, 0.5),
30, 0.3, 0.3, 0.3, 0.1);
// 音效
location.getWorld().playSound(location, org.bukkit.Sound.BLOCK_ANVIL_PLACE, 0.5f, 1.2f);
}
}
/**
* 检查是否为生命方块
*/
private boolean isLifeBlock(Location location) {
// 检查是否有任何玩家的方块在此位置
for (List<Location> playerBlockList : playerBlocks.values()) {
if (playerBlockList.contains(location)) {
return true;
}
}
return false;
}
/**
* 移除被破坏的生命方块
*/
/**
* 移除被破坏的生命方块
*
* @param location 方块位置
* @param breaker 破坏方块的玩家
* @return 移除成功返回true否则返回false
*/
public boolean removeBlock(Location location, Player breaker) {
// 找到此位置属于哪个玩家的方块
UUID ownerId = null;
for (Map.Entry<UUID, List<Location>> entry : playerBlocks.entrySet()) {
if (entry.getValue().contains(location)) {
ownerId = entry.getKey();
break;
}
}
if (ownerId == null) {
return false;
}
List<Location> blocks = playerBlocks.get(ownerId);
if (blocks == null || !blocks.contains(location)) {
return false;
}
// 移除方块
blocks.remove(location);
location.getBlock().setType(Material.AIR);
// 生成破坏效果
spawnBreakEffects(location, breaker);
// 通知所有相关玩家
notifyBlockBreak(ownerId, breaker, blocks.size());
// 检查玩家是否还有剩余方块
if (blocks.isEmpty()) {
handlePlayerElimination(ownerId);
}
return true;
}
/**
* 生成破坏效果
*/
private void spawnBreakEffects(Location location, Player breaker) {
if (location.getWorld() != null) {
// 粒子效果
location.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_CRACK,
location.clone().add(0.5, 0.5, 0.5),
50, 0.5, 0.5, 0.5, 0.5, location.getBlock().getBlockData());
location.getWorld().spawnParticle(org.bukkit.Particle.SMOKE_LARGE,
location.clone().add(0.5, 0.5, 0.5),
20, 0.3, 0.3, 0.3, 0.05);
// 音效
location.getWorld().playSound(location, org.bukkit.Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f);
location.getWorld().playSound(location, org.bukkit.Sound.BLOCK_GLASS_BREAK, 0.8f, 1.0f);
// 对挖掘者造成轻微击退
if (breaker != null) {
Location breakerLoc = breaker.getLocation();
org.bukkit.util.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(ChatColor.RED + "⚠ 警告!你的生命方块被破坏了!");
owner.sendMessage(ChatColor.GRAY + "破坏者: " + ChatColor.YELLOW + (breaker != null ? breaker.getName() : "未知"));
owner.sendMessage(ChatColor.GRAY + "剩余生命方块: " + ChatColor.GREEN + String.valueOf(remaining) + " " + ChatColor.GRAY + "/ " + ChatColor.RED + 5);
if (remaining <= 2) {
owner.sendMessage(ChatColor.DARK_RED + "⚠ 警告!生命方块即将耗尽!");
}
// 播放警告音效
owner.playSound(owner.getLocation(), org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT, 0.8f, 0.5f);
}
// 通知破坏者
if (breaker != null && !breaker.getUniqueId().equals(ownerId)) {
breaker.sendMessage(ChatColor.GOLD + "你破坏了一个生命方块!");
breaker.sendMessage(ChatColor.GRAY + "所有者: " + ChatColor.YELLOW + (ownerName != null ? ownerName : "未知玩家"));
breaker.sendMessage(ChatColor.GRAY + "对方剩余生命方块: " + ChatColor.GREEN + String.valueOf(remaining));
// 给予挖掘者经验奖励
breaker.giveExp(5);
breaker.playSound(breaker.getLocation(), org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
}
// 广播给附近玩家
org.bukkit.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(ChatColor.GRAY + "[附近] " + ChatColor.YELLOW + "一个生命方块被破坏了!");
}
}
}
}
/**
* 处理玩家淘汰
*/
private void handlePlayerElimination(UUID playerId) {
Player player = Bukkit.getPlayer(playerId);
if (player == null || !player.isOnline()) {
return;
}
player.setGameMode(GameMode.SPECTATOR);
player.sendTitle(ChatColor.DARK_RED + "☠ 你被淘汰了!", ChatColor.RED + "所有生命方块已被挖光", 20, 100, 20);
// 从存活玩家列表中移除
alivePlayers.remove(playerId);
// 检查是否只剩一名玩家
checkGameEnd();
}
/**
* 检查游戏是否结束
*/
public void checkGameEnd() {
// 如果限时游戏且时间到了
if (isLimitedTime && gameStartTime > 0) {
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
if (elapsedMinutes >= gameDuration) {
endGame();
return;
}
}
// 如果只剩一名玩家存活
if (alivePlayers.size() <= 1) {
endGame();
}
}
/**
* 结束游戏
*/
private void endGame() {
if (currentState != GameState.STARTED) {
return;
}
setCurrentState(GameState.FINISHED);
Player winner = null;
if (!alivePlayers.isEmpty()) {
winner = Bukkit.getPlayer(alivePlayers.get(0));
}
// 广播胜利消息
for (Player player : Bukkit.getOnlinePlayers()) {
if (winner != null) {
player.sendTitle(ChatColor.GOLD + "🎉 恭喜!", ChatColor.YELLOW + winner.getName() + " 获得了胜利!", 10, 70, 20);
player.sendMessage(ChatColor.GOLD + "🎉 游戏结束!" + ChatColor.YELLOW + winner.getName() + ChatColor.GOLD + " 获得了胜利!");
} else {
player.sendTitle(ChatColor.GOLD + "游戏结束!", ChatColor.YELLOW + "平局!", 10, 70, 20);
player.sendMessage(ChatColor.GOLD + "游戏结束!没有获胜者!");
}
}
}
/**
* 开始限时游戏
*/
/**
* 开始限时游戏
*
* @param minutes 游戏限时(分钟)
*/
public void startTimedGame(int minutes) {
this.gameDuration = minutes;
this.isLimitedTime = true;
startGame();
}
/**
* 开始游戏
*/
public void startGame() {
if (currentState != GameState.WAITING) {
return;
}
setCurrentState(GameState.STARTED);
// 为所有玩家生成生命方块
for (UUID playerId : playerColors.keySet()) {
Player player = Bukkit.getPlayer(playerId);
if (player != null && player.isOnline() && !player.isOp()) { // 只为非OP玩家生成方块
generateLifeBlocksForPlayer(player);
}
}
}
/**
* 重置游戏
*/
public void resetGame() {
// 清空所有数据
playerColors.clear();
playerBlocks.clear();
alivePlayers.clear();
gameDuration = 0;
isLimitedTime = false;
gameStartTime = 0;
// 将所有玩家恢复到适当模式,并重新分配颜色
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.isOp()) {
player.setGameMode(GameMode.SPECTATOR);
} else {
player.setGameMode(GameMode.ADVENTURE);
player.getInventory().clear();
// 重新分配颜色
assignPlayerColor(player);
}
}
setCurrentState(GameState.WAITING);
}
/**
* 获取玩家剩余方块数量
*/
/**
* 获取玩家剩余方块数量
*
* @param playerId 玩家UUID
* @return 玩家剩余方块数量
*/
public int getRemainingBlocks(UUID playerId) {
List<Location> blocks = playerBlocks.get(playerId);
return blocks != null ? blocks.size() : 0;
}
/**
* 获取玩家颜色
*/
/**
* 获取玩家颜色
*
* @param playerId 玩家UUID
* @return 玩家的颜色材料
*/
public Material getPlayerColor(UUID playerId) {
return playerColors.get(playerId);
}
/**
* 更新计分板
*/
public void updateScoreboard() {
if (gameObjective == null) {
return;
}
// 清空现有计分板条目
for (String entry : gameScoreboard.getEntries()) {
gameScoreboard.resetScores(entry);
}
// 设置标题
gameObjective.setDisplayName(ChatColor.GOLD + "PlayerBlockLife 游戏");
// 添加内容
int line = 1;
String statusText = "";
switch (currentState) {
case WAITING:
statusText = ChatColor.YELLOW + "等待中...";
break;
case STARTED:
statusText = ChatColor.GREEN + "开始游戏";
break;
case FINISHED:
statusText = ChatColor.RED + "游戏结束";
break;
}
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.WHITE + "").setScore(line++);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "状态:").setScore(line++);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(statusText).setScore(line++);
// 如果是游戏中,显示剩余时间和玩家方块数
if (currentState == GameState.STARTED) {
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
// 显示剩余时间(如果有限时)
if (isLimitedTime) {
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
long remainingMinutes = Math.max(0, gameDuration - elapsedMinutes);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "剩余时间:").setScore(line++);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.YELLOW + String.valueOf(remainingMinutes) + "分钟").setScore(line++);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
}
// 显示当前玩家剩余方块数
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "我的方块:").setScore(line++);
// 这个会在PlayerJoinListener中根据每个玩家更新
}
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
gameScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.GRAY + "PlayerBlockLife v" + plugin.getDescription().getVersion()).setScore(line++);
// 给在线玩家设置计分板
for (Player player : Bukkit.getOnlinePlayers()) {
player.setScoreboard(gameScoreboard);
}
}
/**
* 更新玩家特定的计分板信息
*/
/**
* 更新玩家特定的计分板信息
*
* @param player 要更新计分板的玩家
*/
public void updatePlayerScoreboard(Player player) {
if (currentState == GameState.STARTED && gameObjective != null) {
// 为特定玩家更新剩余方块数
int remainingBlocks = getRemainingBlocks(player.getUniqueId());
String blocksText = ChatColor.YELLOW + String.valueOf(remainingBlocks) + "/5";
// 临时更新计分板,为特定玩家显示他们的剩余方块
Scoreboard playerScoreboard = player.getScoreboard();
if (playerScoreboard != gameScoreboard) {
playerScoreboard = gameScoreboard;
}
// 为每个玩家创建一个自定义的计分板
Scoreboard customScoreboard = scoreboardManager.getNewScoreboard();
Objective customObjective = customScoreboard.registerNewObjective("pbl_player", "dummy", ChatColor.GOLD + "PlayerBlockLife 游戏");
customObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
// 复制原始计分板内容
int line = 1;
String statusText = "";
switch (currentState) {
case WAITING:
statusText = ChatColor.YELLOW + "等待中...";
break;
case STARTED:
statusText = ChatColor.GREEN + "开始游戏";
break;
case FINISHED:
statusText = ChatColor.RED + "游戏结束";
break;
}
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.WHITE + "").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "状态:").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(statusText).setScore(line++);
if (isLimitedTime) {
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - gameStartTime) / (1000 * 60);
long remainingMinutes = Math.max(0, gameDuration - elapsedMinutes);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "剩余时间:").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.YELLOW + String.valueOf(remainingMinutes) + "分钟").setScore(line++);
}
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.AQUA + "我的方块:").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(blocksText).setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(" ").setScore(line++);
customScoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(ChatColor.GRAY + "PlayerBlockLife v" + plugin.getDescription().getVersion()).setScore(line++);
player.setScoreboard(customScoreboard);
}
}
/**
* 获取存活玩家数量
*/
/**
* 获取存活玩家数量
*
* @return 存活玩家数量
*/
public int getAlivePlayersCount() {
return alivePlayers.size();
}
/**
* 获取所有存活玩家
*/
/**
* 获取所有存活玩家
*
* @return 存活玩家UUID列表
*/
public List<UUID> getAlivePlayers() {
return new ArrayList<>(alivePlayers);
}
/**
* 获取所有玩家的方块位置
*/
/**
* 获取所有玩家的方块位置
*
* @return 玩家UUID到方块位置列表的映射
*/
public Map<UUID, List<Location>> getPlayerBlocks() {
return new HashMap<>(playerBlocks);
}
/**
* 清除指定玩家的方块
*/
/**
* 清除指定玩家的方块
*
* @param playerId 要清除方块的玩家UUID
*/
public void clearPlayerBlocks(UUID playerId) {
List<Location> blocks = playerBlocks.remove(playerId);
if (blocks != null) {
for (Location loc : blocks) {
if (loc.getWorld() != null) {
loc.getBlock().setType(org.bukkit.Material.AIR);
}
}
}
// 从存活玩家列表中移除
alivePlayers.remove(playerId);
}
/**
* 获取游戏持续时间(分钟)
*/
/**
* 获取游戏持续时间(分钟)
*
* @return 游戏持续时间(分钟)
*/
public int getGameDuration() {
return gameDuration;
}
/**
* 获取游戏开始时间戳
*/
/**
* 获取游戏开始时间戳
*
* @return 游戏开始时间戳
*/
public long getGameStartTime() {
return gameStartTime;
}
/**
* 检查是否为限时游戏
*/
/**
* 检查是否为限时游戏
*
* @return 如果是限时游戏返回true否则返回false
*/
public boolean isLimitedTime() {
return isLimitedTime;
}
}

View File

@@ -33,7 +33,7 @@ import java.util.UUID;
* </pre>
*
* @author xiaobai
* @version 2.1.0
* @version 2.2.0
* @since 1.0.0
*/
public class LifeSystem {
@@ -46,8 +46,14 @@ public class LifeSystem {
}
public void checkAllPlayers() {
for (Player player : Bukkit.getOnlinePlayers()) {
checkPlayerHealth(player);
// 只在游戏进行中时检查玩家生命值,等待和旁观状态的玩家不检查
if (plugin.getGameStateManager().getCurrentState() == GameStateManager.GameState.STARTED) {
for (Player player : Bukkit.getOnlinePlayers()) {
// 只检查生存模式的玩家(非旁观者)
if (player.getGameMode() == GameMode.SURVIVAL) {
checkPlayerHealth(player);
}
}
}
}

View File

@@ -16,6 +16,11 @@ public class MessageManager {
private File messageFile;
private final Map<String, String> messageCache = new HashMap<>();
/**
* 构造一个新的消息管理器
*
* @param plugin 插件主类实例
*/
public MessageManager(PlayerBlockLife plugin) {
this.plugin = plugin;
this.messageFile = new File(plugin.getDataFolder(), "messages.yml");
@@ -86,6 +91,13 @@ public class MessageManager {
/**
* 获取消息
*/
/**
* 获取消息
*
* @param path 消息路径
* @param defaultValue 默认值
* @return 消息内容
*/
public String getMessage(String path, String defaultValue) {
ConfigManager config = plugin.getConfigManager();
@@ -122,6 +134,14 @@ public class MessageManager {
/**
* 获取格式化消息(替换变量)
*/
/**
* 获取格式化消息(替换变量)
*
* @param path 消息路径
* @param defaultValue 默认值
* @param variables 变量映射
* @return 格式化后的消息内容
*/
public String getFormattedMessage(String path, String defaultValue, Map<String, String> variables) {
String message = getMessage(path, defaultValue);
@@ -137,6 +157,13 @@ public class MessageManager {
/**
* 获取控制台消息
*/
/**
* 获取控制台消息
*
* @param path 消息路径
* @param defaultValue 默认值
* @return 控制台消息内容
*/
public String getConsoleMessage(String path, String defaultValue) {
String message = getMessage("console." + path, defaultValue);
// 移除颜色代码(控制台不需要)
@@ -149,6 +176,13 @@ public class MessageManager {
/**
* 获取游戏内消息
*/
/**
* 获取游戏内消息
*
* @param path 消息路径
* @param defaultValue 默认值
* @return 游戏内消息内容
*/
public String getGameMessage(String path, String defaultValue) {
return getMessage("game." + path, defaultValue);
}
@@ -156,6 +190,14 @@ public class MessageManager {
/**
* 获取命令消息
*/
/**
* 获取命令消息
*
* @param command 命令名称
* @param path 消息路径
* @param defaultValue 默认值
* @return 命令消息内容
*/
public String getCommandMessage(String command, String path, String defaultValue) {
return getMessage("commands." + command + "." + path, defaultValue);
}
@@ -163,6 +205,13 @@ public class MessageManager {
/**
* 获取广播消息
*/
/**
* 获取广播消息
*
* @param path 消息路径
* @param defaultValue 默认值
* @return 广播消息内容
*/
public String getBroadcastMessage(String path, String defaultValue) {
return getMessage("broadcast." + path, defaultValue);
}
@@ -170,6 +219,11 @@ public class MessageManager {
/**
* 检查消息文件是否存在
*/
/**
* 检查消息文件是否存在
*
* @return 如果外部消息文件存在则返回true否则返回false
*/
public boolean hasExternalMessageFile() {
return messageFile.exists();
}
@@ -177,6 +231,11 @@ public class MessageManager {
/**
* 获取消息文件路径
*/
/**
* 获取消息文件路径
*
* @return 消息文件的绝对路径
*/
public String getMessageFilePath() {
return messageFile.getAbsolutePath();
}

View File

@@ -0,0 +1,139 @@
package com.playerblocklife;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* PlayerBlockLife命令执行器 - 处理PlayerBlockLife游戏模式的专用命令
*
* <p>提供PlayerBlockLife游戏模式的核心控制命令包括
* <ul>
* <li>/PlayerBlockLife start [时间] - 开始游戏,支持可选的限时模式</li>
* <li>/PlayerBlockLife rstgm - 重置游戏,重新分配玩家颜色</li>
* </ul>
*
* <p>这些命令仅允许服务器管理员OP执行用于控制PlayerBlockLife游戏的生命周期。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 4.0.0
*/
public class PBLCommands implements CommandExecutor {
private final PlayerBlockLife plugin;
public PBLCommands(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player) && !sender.hasPermission("playerblocklife.admin")) {
// 控制台默认有权限
if (!sender.isOp() && !sender.hasPermission("playerblocklife.admin")) {
sender.sendMessage(ChatColor.RED + "你没有权限执行此命令!");
return true;
}
} else if (sender instanceof Player) {
Player player = (Player) sender;
if (!player.isOp() && !player.hasPermission("playerblocklife.admin")) {
player.sendMessage(ChatColor.RED + "你没有权限执行此命令!");
return true;
}
}
if (args.length == 0) {
if (sender instanceof Player) {
showHelp((Player) sender);
} else {
showConsoleHelp(sender);
}
return true;
}
String subCommand = args[0].toLowerCase();
if (subCommand.equals("start")) {
handleStartCommand(sender, args);
} else if (subCommand.equals("rstgm") || subCommand.equals("reset")) {
handleResetCommand(sender);
} else {
if (sender instanceof Player) {
showHelp((Player) sender);
} else {
showConsoleHelp(sender);
}
}
return true;
}
private void handleStartCommand(CommandSender sender, String[] args) {
if (plugin.getGameStateManager().getCurrentState() == GameStateManager.GameState.STARTED) {
sender.sendMessage(ChatColor.RED + "游戏已在进行中!");
return;
}
int duration = 0;
boolean isTimed = false;
// 检查是否有时间参数
if (args.length > 1) {
try {
duration = Integer.parseInt(args[1]);
if (duration <= 0) {
sender.sendMessage(ChatColor.RED + "游戏时间必须大于0分钟");
return;
}
isTimed = true;
sender.sendMessage(ChatColor.GREEN + "限时游戏开始,持续时间: " + duration + " 分钟!");
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "无效的时间参数!请输入分钟数,例如: /pbl start 30");
return;
}
} else {
sender.sendMessage(ChatColor.GREEN + "游戏开始!");
}
// 开始游戏
if (isTimed) {
plugin.getGameStateManager().startTimedGame(duration);
} else {
plugin.getGameStateManager().startGame();
}
// 广播游戏开始消息
for (Player onlinePlayer : org.bukkit.Bukkit.getOnlinePlayers()) {
onlinePlayer.sendMessage(ChatColor.GOLD + "游戏开始!保护好你的生命方块!");
if (isTimed) {
onlinePlayer.sendMessage(ChatColor.YELLOW + "限时: " + duration + " 分钟");
}
}
}
private void handleResetCommand(CommandSender sender) {
plugin.getGameStateManager().resetGame();
sender.sendMessage(ChatColor.YELLOW + "游戏已重置,所有玩家回到等待状态。");
// 广播重置消息
for (Player onlinePlayer : org.bukkit.Bukkit.getOnlinePlayers()) {
onlinePlayer.sendMessage(ChatColor.GOLD + "游戏已重置,请等待重新分配颜色。");
}
}
private void showHelp(Player player) {
player.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 命令 ===");
player.sendMessage(ChatColor.YELLOW + "/pbl start [时间(分钟)] - 开始游戏,可选限时");
player.sendMessage(ChatColor.YELLOW + "/pbl rstgm - 重置游戏");
player.sendMessage(ChatColor.GRAY + "注:只有管理员(权限)可以执行这些命令");
}
private void showConsoleHelp(CommandSender sender) {
sender.sendMessage(ChatColor.GOLD + "=== PlayerBlockLife 命令 ===");
sender.sendMessage(ChatColor.YELLOW + "/pbl start [时间(分钟)] - 开始游戏,可选限时");
sender.sendMessage(ChatColor.YELLOW + "/pbl rstgm - 重置游戏");
sender.sendMessage(ChatColor.GRAY + "注:控制台拥有执行这些命令的权限");
}
}

View File

@@ -6,22 +6,25 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level;
/**
* PlayerBlockLife插件主类 - 玩家生命方块系统的核心控制器
* PlayerBlockLife插件主类 - PlayerBlockLife生存游戏模式的核心控制器
*
* <p>这个插件为Minecraft服务器添加了一个独特的游戏机制:每个玩家拥有一定数量的生命方块,
* 这些方块使用玩家的皮肤作为材质。当其他玩家挖光某个玩家的所有生命方块时,该玩家会被淘汰。</p>
* <p>这个插件为Minecraft服务器添加了一个独特的生存游戏模式每个非OP玩家拥有一定数量的生命方块,
* 这些方块使用不同颜色的羊毛、玻璃或水泥方块表示。当其他玩家挖光某个玩家的所有生命方块时,该玩家会被淘汰。
* 游戏需要管理员使用/pbl start命令开始支持限时模式最后存活的玩家获胜。</p>
*
* <p>主要功能:
* <p>主要功能:</p>
* <ul>
* <li>管理玩家生命方块的生成和销毁</li>
* <li>处理玩家皮肤的获取和应用</li>
* <li>监控玩家生命值状态</li>
* <li>提供完整的命令和权限系统</li>
* <li>支持配置热重载和数据持久化</li>
* <li>管理PlayerBlockLife游戏的完整生命周期等待、进行、结束</li>
* <li>为非OP玩家分配独特的颜色生命方块</li>
* <li>处理生命方块的生成和销毁</li>
* <li>监控游戏状态和玩家存活情况</li>
* <li>提供PlayerBlockLife专用命令系统/pbl start, /pbl rstgm</li>
* <li>支持游戏计分板显示</li>
* <li>管理员可使用传统命令进行管理</li>
* </ul>
*
* @author xiaobai
* @version 3.0.0-experimental-1.20.4
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class PlayerBlockLife extends JavaPlugin {
@@ -31,6 +34,7 @@ public class PlayerBlockLife extends JavaPlugin {
private LifeSystem lifeSystem;
private ConfigManager configManager;
private MessageManager messageManager;
private GameStateManager gameStateManager;
/**
* 插件启用时调用,执行初始化操作
@@ -78,12 +82,16 @@ public class PlayerBlockLife extends JavaPlugin {
getCommand("pbldelete").setExecutor(new AdminCommands(this));
getCommand("pblrevive").setExecutor(new AdminCommands(this));
getCommand("pblstats").setExecutor(new AdminCommands(this));
getCommand("pbl").setExecutor(new PBLCommands(this));
// 第六步:加载其他数据
blockManager.loadData();
skinManager.loadAllSkins();
// 第六步:初始化游戏状态管理器(先于其他数据加载)
this.gameStateManager = new GameStateManager(this);
// 第七步:启动定时任务
// 第七步:加载其他数据
// 注意在新模式下我们不再使用原有的blockManager和skinManager
// 因此不再调用blockManager.loadData()和skinManager.loadAllSkins()
// 第八步:启动定时任务
startScheduler();
getLogger().info("§a========================================");
@@ -172,6 +180,10 @@ public class PlayerBlockLife extends JavaPlugin {
if (lifeSystem != null) {
lifeSystem.checkAllPlayers();
}
// 每10秒检查游戏结束条件
if (gameStateManager != null) {
gameStateManager.checkGameEnd();
}
}, 200L, 200L);
// 每分钟清理一次过期缓存
@@ -180,6 +192,13 @@ public class PlayerBlockLife extends JavaPlugin {
skinManager.cleanupOldCache();
}
}, 1200L, 1200L);
// 每秒更新计分板
getServer().getScheduler().runTaskTimer(this, () -> {
if (gameStateManager != null) {
gameStateManager.updateScoreboard();
}
}, 20L, 20L);
}
/**
@@ -194,34 +213,84 @@ public class PlayerBlockLife extends JavaPlugin {
return instance;
}
/**
* 获取方块管理器
*
* @return 方块管理器实例
*/
public PlayerBlockManager getBlockManager() {
return blockManager;
}
/**
* 获取皮肤管理器
*
* @return 皮肤管理器实例
*/
public SkinManager getSkinManager() {
return skinManager;
}
/**
* 获取生命值系统
*
* @return 生命值系统实例
*/
public LifeSystem getLifeSystem() {
return lifeSystem;
}
/**
* 获取配置管理器
*
* @return 配置管理器实例
*/
public ConfigManager getConfigManager() {
return configManager;
}
/**
* 获取消息管理器
*
* @return 消息管理器实例
*/
public MessageManager getMessageManager() {
return messageManager;
}
/**
* 获取游戏状态管理器
*
* @return 游戏状态管理器实例
*/
public GameStateManager getGameStateManager() {
return gameStateManager;
}
/**
* 记录信息级别日志
*
* @param message 日志消息
*/
public void logInfo(String message) {
getLogger().info(message);
}
/**
* 记录警告级别日志
*
* @param message 日志消息
*/
public void logWarning(String message) {
getLogger().warning(message);
}
/**
* 记录错误级别日志
*
* @param message 日志消息
* @param throwable 异常对象
*/
public void logError(String message, Throwable throwable) {
getLogger().log(Level.SEVERE, message, throwable);
}

View File

@@ -38,12 +38,11 @@ import java.util.concurrent.ConcurrentHashMap;
* <li>支持异步皮肤加载,避免方块放置阻塞</li>
* <li>提供皮肤加载状态检查,确保皮肤就绪后再放置方块</li>
* </ul>
* </p>
*
* <p>使用并发安全的数据结构确保多线程环境下的数据一致性。</p>
*
* @author xiaobai
* @version 2.1.0
* @version 2.2.0
* @since 1.0.0
*/
public class PlayerBlockManager {
@@ -66,12 +65,10 @@ public class PlayerBlockManager {
*/
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();
boolean requireOpenSky = true; // 使用默认值,因为配置已移除
int maxAttempts = 50; // 使用默认值,因为配置已移除
return generateLifeBlocksForPlayer(player, blockAmount, spreadRange, requireOpenSky, maxAttempts);
return generateLifeBlocksForPlayer(player, 5, 5, requireOpenSky, maxAttempts);
}
/**

View File

@@ -13,9 +13,32 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* PlayerJoinListener - PlayerBlockLife游戏模式下的玩家加入和重生监听器
*
* <p>处理玩家加入服务器和重生时的逻辑,包括:
* <ul>
* <li>根据玩家权限设置游戏模式OP玩家为观察者模式非OP玩家为冒险模式</li>
* <li>为非OP玩家分配独特的生命方块颜色</li>
* <li>给非OP玩家提供对应颜色的方块作为手持物品</li>
* <li>根据当前游戏状态显示相应信息</li>
* <li>处理玩家重生时的游戏状态恢复</li>
* </ul>
*
* <p>此监听器与GameStateManager协作确保玩家正确加入PlayerBlockLife游戏模式。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class PlayerJoinListener implements Listener {
private final PlayerBlockLife plugin;
/**
* 构造一个新的玩家加入监听器
*
* @param plugin 插件主类实例
*/
public PlayerJoinListener(PlayerBlockLife plugin) {
this.plugin = plugin;
}
@@ -25,70 +48,34 @@ public class PlayerJoinListener implements Listener {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
// 异步加载玩家皮肤
plugin.getSkinManager().loadPlayerSkinAsync(player);
// 根据玩家权限设置游戏模式
if (player.isOp()) {
// OP玩家设置为观察者模式
player.setGameMode(GameMode.SPECTATOR);
player.sendMessage(org.bukkit.ChatColor.YELLOW + "您是管理员,已设置为观察者模式。");
} else {
// 非OP玩家设置为冒险模式
player.setGameMode(GameMode.ADVENTURE);
// 分配颜色并给玩家对应颜色的方块
plugin.getGameStateManager().assignPlayerColor(player);
}
// 延迟执行,确保皮肤加载完成
// 延迟执行,确保颜色分配完成
Bukkit.getScheduler().runTaskLater(plugin, () -> {
int remainingBlocks = plugin.getBlockManager().getRemainingBlocks(playerId);
// 检查是否启用自动生成
if (plugin.getConfigManager().isAutoGenerationEnabled() && remainingBlocks == 0) {
// 自动生成生命方块
generateLifeBlocksForPlayer(player);
} else if (remainingBlocks > 0) {
// 已有方块,显示欢迎信息
Map<String, String> variables = new HashMap<>();
variables.put("player", player.getName());
variables.put("remaining", String.valueOf(remainingBlocks));
variables.put("total", String.valueOf(plugin.getConfigManager().getBlocksPerPlayer()));
String welcomeMsg = plugin.getMessageManager().getFormattedMessage("game.player.welcome_back",
"&e欢迎回来&6{player}&e", variables);
String blocksMsg = plugin.getMessageManager().getFormattedMessage("game.player.remaining_blocks",
"&7你还有 &e{remaining} &7个生命方块", variables);
String checkMsg = plugin.getMessageManager().getMessage("game.player.check_blocks_hint",
"&7使用 &e/checklifeblocks &7查看方块位置");
player.sendMessage("§a========================================");
player.sendMessage(welcomeMsg);
player.sendMessage(blocksMsg);
player.sendMessage(checkMsg);
player.sendMessage("§a========================================");
if (remainingBlocks <= 2) {
String warningMsg = plugin.getMessageManager().getMessage("game.block.warning_low_blocks",
"&c⚠ 警告!你的生命方块即将耗尽!");
player.sendMessage(warningMsg);
player.playSound(player.getLocation(),
org.bukkit.Sound.ENTITY_WITHER_SPAWN, 0.5f, 1.0f);
}
} else {
// 没有方块且自动生成未启用
String welcomeMsg = plugin.getMessageManager().getMessage("game.player.welcome_new",
"&e欢迎加入游戏");
String setBlocksMsg = plugin.getMessageManager().getMessage("game.player.set_blocks_hint",
"&7使用 &e/setlifeblocks &7来设置你的生命方块");
String rulesTitle = plugin.getMessageManager().getMessage("game.player.rules_title",
"&6游戏规则");
String rule1 = plugin.getMessageManager().getMessage("game.player.rule1",
"&7- 每个玩家有5个生命方块");
String rule2 = plugin.getMessageManager().getMessage("game.player.rule2",
"&7- 方块被其他玩家挖光时,你将死亡");
String rule3 = plugin.getMessageManager().getMessage("game.player.rule3",
"&7- 方块使用你的皮肤作为材质");
String rule4 = plugin.getMessageManager().getMessage("game.player.rule4",
"&7- 你可以自由移动,但方块固定位置");
player.sendMessage(welcomeMsg);
player.sendMessage(setBlocksMsg);
player.sendMessage(rulesTitle);
player.sendMessage(rule1);
player.sendMessage(rule2);
player.sendMessage(rule3);
player.sendMessage(rule4);
// 更新玩家的计分板
plugin.getGameStateManager().updatePlayerScoreboard(player);
// 根据游戏状态显示不同信息
GameStateManager.GameState currentState = plugin.getGameStateManager().getCurrentState();
if (currentState == GameStateManager.GameState.WAITING) {
player.sendMessage(org.bukkit.ChatColor.YELLOW + "游戏等待中,请等待管理员开始游戏。");
} else if (currentState == GameStateManager.GameState.STARTED) {
player.sendMessage(org.bukkit.ChatColor.GREEN + "游戏中,请小心保护你的生命方块!");
} else if (currentState == GameStateManager.GameState.FINISHED) {
player.sendMessage(org.bukkit.ChatColor.RED + "游戏已结束,请等待下一轮开始。");
}
}, 40L);
}, 20L);
}
/**
@@ -98,14 +85,12 @@ public class PlayerJoinListener implements Listener {
try {
// 获取配置
ConfigManager config = plugin.getConfigManager();
int blockAmount = config.getBlocksPerPlayer();
int spreadRange = config.getSpreadRange();
boolean requireOpenSky = config.isRequireOpenSky();
int maxAttempts = config.getMaxAttempts();
boolean requireOpenSky = true; // 使用默认值,因为配置已移除
int maxAttempts = 50; // 使用默认值,因为配置已移除
// 调用方块管理器生成方块
boolean success = plugin.getBlockManager().generateLifeBlocksForPlayer(
player, blockAmount, spreadRange, requireOpenSky, maxAttempts
player, 5, 5, requireOpenSky, maxAttempts
);
if (success) {
@@ -113,7 +98,7 @@ public class PlayerJoinListener implements Listener {
String message = plugin.getMessageManager().getMessage("console.blocks_generated",
"&a[PlayerBlockLife] 已为玩家 {player} 生成 {amount} 个生命方块");
message = message.replace("{player}", player.getName())
.replace("{amount}", String.valueOf(blockAmount));
.replace("{amount}", String.valueOf(5)); // 使用固定值5
// 移除颜色代码用于日志
String logMessage = message.replace("&", "");
plugin.getLogger().info(logMessage);
@@ -121,7 +106,7 @@ public class PlayerJoinListener implements Listener {
// 发送给玩家
String playerMsg = plugin.getMessageManager().getMessage("game.block.placed",
"&a已为你生成 {amount} 个生命方块!");
playerMsg = playerMsg.replace("{amount}", String.valueOf(blockAmount))
playerMsg = playerMsg.replace("{amount}", String.valueOf(5)) // 使用固定值5
.replace("&", "§");
player.sendMessage(playerMsg);
} else {
@@ -134,7 +119,7 @@ public class PlayerJoinListener implements Listener {
plugin.getLogger().warning(logFailureMsg);
// 根据配置处理失败
String onFailure = config.getOnFailureAction();
String onFailure = "notify"; // 使用默认值,因为配置已移除
if (onFailure.equals("notify")) {
String notifyMsg = plugin.getMessageManager().getMessage("game.errors.cannot_generate_blocks",
"&c无法生成生命方块{reason}");
@@ -168,14 +153,28 @@ public class PlayerJoinListener implements Listener {
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
if (!plugin.getLifeSystem().isPlayerAlive(playerId)) {
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (player.isOnline()) {
player.setGameMode(GameMode.SPECTATOR);
player.sendMessage("§e你已被淘汰可以观察其他玩家。");
player.sendMessage("§7等待下一轮游戏开始...");
}
}, 20L);
// 根据当前游戏状态处理玩家重生
GameStateManager.GameState currentState = plugin.getGameStateManager().getCurrentState();
if (currentState == GameStateManager.GameState.STARTED) {
// 如果游戏中,检查玩家是否存活
int remainingBlocks = plugin.getGameStateManager().getRemainingBlocks(playerId);
if (remainingBlocks <= 0) {
// 玩家已被淘汰,设置为观察者模式
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (player.isOnline()) {
player.setGameMode(GameMode.SPECTATOR);
player.sendMessage(org.bukkit.ChatColor.YELLOW + "你已被淘汰,可以观察其他玩家。");
}
}, 20L);
}
} else {
// 如果游戏未开始或已结束OP玩家保持观察者模式非OP玩家设置为冒险模式
if (player.isOp()) {
player.setGameMode(GameMode.SPECTATOR);
} else {
player.setGameMode(GameMode.ADVENTURE);
plugin.getGameStateManager().assignPlayerColor(player);
}
}
}
}

View File

@@ -4,6 +4,16 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* PlayerQuitListener - PlayerBlockLife游戏模式下的玩家退出监听器
*
* <p>处理玩家退出服务器时的日志记录。在PlayerBlockLife游戏模式中玩家退出不会影响
* 其生命方块状态,因为方块位置信息仅在服务器运行期间维护。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class PlayerQuitListener implements Listener {
private final PlayerBlockLife plugin;

View File

@@ -1,14 +1,22 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.UUID;
/**
* SetLifeBlocksCommand - 在新模式下提示命令不可用
*
* <p>在PlayerBlockLife游戏模式下此命令不再用于手动设置生命方块。
* 生命方块将在游戏开始时自动为每个非OP玩家生成。
* 玩家应等待管理员使用 /PlayerBlockLife start 命令开始游戏。</p>
*
* @author xiaobai
* @version 2.2.0-1.20.4
* @since 1.0.0
*/
public class SetLifeBlocksCommand implements CommandExecutor {
private final PlayerBlockLife plugin;
@@ -18,167 +26,18 @@ public class SetLifeBlocksCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// 检查命令是否启用
if (!plugin.getConfigManager().isCommandEnabled("setlifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.command_disabled",
"&c此命令已被禁用");
sender.sendMessage(message);
return true;
}
if (!(sender instanceof Player)) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c只有玩家可以使用此命令");
sender.sendMessage(message);
sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!控制台无法执行此命令。");
return true;
}
Player player = (Player) sender;
UUID playerId = player.getUniqueId();
if (args.length > 0 && args[0].equalsIgnoreCase("help")) {
showHelp(player);
return true;
}
// 检查管理员权限和配置
if (args.length > 0 && args[0].equalsIgnoreCase("other")) {
// 检查是否允许管理员使用
if (!plugin.getConfigManager().isAdminUseAllowed("setlifeblocks") || !player.hasPermission("playerblocklife.admin")) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
player.sendMessage(message);
return true;
}
if (args.length < 2) {
String usage = plugin.getMessageManager().getCommandMessage("setlifeblocks", "usage",
"&c用法: /setlifeblocks [reset|other|help]");
player.sendMessage(usage);
return true;
}
Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
String message = plugin.getMessageManager().getMessage("game.errors.player_not_found",
"&c找不到玩家: {player}");
message = message.replace("{player}", args[1]);
player.sendMessage(message);
return true;
}
setBlocksForPlayer(target, player);
return true;
}
// 在新模式下setlifeblocks命令不可用
player.sendMessage(ChatColor.RED + "此命令在新模式下不可用。");
player.sendMessage(ChatColor.YELLOW + "生命方块将在游戏开始时自动分配。");
player.sendMessage(ChatColor.GRAY + "请等待管理员使用 /PlayerBlockLife start 开始游戏。");
// 检查是否允许玩家自己使用
if (!plugin.getConfigManager().isSelfUseAllowed("setlifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
player.sendMessage(message);
return true;
}
setBlocksForPlayer(player, null);
return true;
}
private void setBlocksForPlayer(Player target, Player executor) {
UUID targetId = target.getUniqueId();
MessageManager msgManager = plugin.getMessageManager();
if (plugin.getBlockManager().hasLifeBlocks(targetId)) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.errors.player_already_has_blocks",
"&c玩家 {player} 已经有生命方块了!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
String alreadyHas = msgManager.getCommandMessage("setlifeblocks", "already_has",
"&c你已经有生命方块了使用 /checklifeblocks 查看位置");
target.sendMessage(alreadyHas);
}
return;
}
if (!plugin.getSkinManager().isSkinLoaded(targetId)) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.skin_loading",
"&e玩家 {player} 的皮肤正在加载中,请稍候...");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
target.sendMessage("§e你的皮肤正在加载中请稍候...");
target.sendMessage("§7(如果长时间未加载完成,请重新加入服务器)");
}
plugin.getSkinManager().loadPlayerSkinAsync(target);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
boolean success = plugin.getBlockManager().setLifeBlocks(target, target.getLocation());
if (success) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.blocks_generated_for_other",
"&a已为玩家 {player} 生成生命方块!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
String successMsg = msgManager.getCommandMessage("setlifeblocks", "success",
"&a已为你生成 {blocks} 个生命方块!");
ConfigManager config = plugin.getConfigManager();
successMsg = successMsg.replace("{blocks}", String.valueOf(config.getBlocksPerPlayer()));
target.sendMessage(successMsg);
}
} else {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.errors.failed_to_generate_blocks",
"&c为玩家 {player} 生成生命方块失败!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
target.sendMessage("§c生成失败请稍后再试或联系管理员");
}
}
}, 40L);
return;
}
boolean success = plugin.getBlockManager().setLifeBlocks(target, target.getLocation());
if (success) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.blocks_generated_for_other",
"&a已为玩家 {player} 生成生命方块!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
String successMsg = msgManager.getCommandMessage("setlifeblocks", "success",
"&a已为你生成 {blocks} 个生命方块!");
ConfigManager config = plugin.getConfigManager();
successMsg = successMsg.replace("{blocks}", String.valueOf(config.getBlocksPerPlayer()));
target.sendMessage(successMsg);
}
} else {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
String message = msgManager.getMessage("game.errors.failed_to_generate_blocks",
"&c为玩家 {player} 生成生命方块失败!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
target.sendMessage("§c生成失败请确保周围有足够空间");
}
}
}
private void showHelp(Player player) {
String helpMessage = plugin.getMessageManager().getCommandMessage("setlifeblocks", "help",
"&6=== PlayerBlockLife 帮助 ===\n" +
"&e/setlifeblocks &7- 设置你的生命方块\n" +
"&e/setlifeblocks reset &7- 重置生命方块位置\n" +
"&e/setlifeblocks other <玩家> &7- 为其他玩家设置(管理员)\n" +
"&e/setlifeblocks help &7- 显示此帮助");
player.sendMessage(helpMessage);
}
}

View File

@@ -52,7 +52,7 @@ import java.util.concurrent.ConcurrentHashMap;
* <p>皮肤缓存默认保留7天过期后自动重新获取。</p>
*
* @author xiaobai
* @version 2.1.0
* @version 2.2.0
* @since 1.0.0
*/
public class SkinManager {
@@ -97,7 +97,7 @@ public class SkinManager {
plugin.logInfo("开始加载皮肤: " + player.getName());
String skinBase64 = null;
String skinSource = plugin.getConfigManager().getSkinSource();
String skinSource = "skinsrestorer"; // 使用默认值,因为配置已移除
// 根据配置的皮肤来源优先级获取皮肤
if ("skinsrestorer".equalsIgnoreCase(skinSource)) {
@@ -110,7 +110,7 @@ public class SkinManager {
} else if ("player_profile".equalsIgnoreCase(skinSource)) {
// 优先尝试PlayerProfile
skinBase64 = getSkinFromPlayerProfile(player);
if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
if (skinBase64 == null && true) {
plugin.logInfo("PlayerProfile获取失败尝试SkinsRestorer: " + player.getName());
skinBase64 = getSkinFromSkinsRestorer(player);
}
@@ -122,7 +122,7 @@ public class SkinManager {
}
// 缓存不存在,尝试其他来源
skinBase64 = getSkinFromPlayerProfile(player);
if (skinBase64 == null && plugin.getConfigManager().useSkinsRestorer()) {
if (skinBase64 == null && true) {
skinBase64 = getSkinFromSkinsRestorer(player);
}
}