6 Commits
1.0.1 ... 2.0.0

Author SHA1 Message Date
xiaobai
2fbf5cfd7d 修复bug:破坏方块无反应 2026-02-13 22:27:03 +08:00
xiaobai
8b502459b0 1.0.1:Fix bug:[21:01:10 INFO]: [PBL] §a========================================
[21:01:10 INFO]: [PBL] §ePlayerBlockLife v1.0.1-1.20.4 已启用
              [21:01:10 INFO]: [PBL] §e作者: [YourName]
              [21:01:10 INFO]: [PBL] §a========================================
2026-02-13 22:03:17 +08:00
xiaobai
f899540449 1.0.1:Fix bug:[21:01:10 INFO]: [PBL] §a========================================
[21:01:10 INFO]: [PBL] §ePlayerBlockLife v1.0.1-1.20.4 已启用
              [21:01:10 INFO]: [PBL] §e作者: [YourName]
              [21:01:10 INFO]: [PBL] §a========================================
2026-02-13 21:02:28 +08:00
xiaobai
1fbd92ec72 1.0.1:Fix error:[DirectoryProviderSource] Error loading plugin: name is not defined 2026-02-13 20:59:33 +08:00
xiaobai
c15b30f666 1.0.1:Fix error:[DirectoryProviderSource] Error loading plugin: name is not defined 2026-02-13 20:58:03 +08:00
xiaobai
8d8cdcb244 1.0.1:Fix error:[DirectoryProviderSource] Error loading plugin: name is not defined 2026-02-13 20:56:03 +08:00
15 changed files with 1217 additions and 483 deletions

View File

@@ -4,7 +4,7 @@ plugins {
}
group = 'com.playerblocklife'
version = '1.0.1-1.20.4'
version = '2.0.0-1.20.4'
sourceCompatibility = 17
targetCompatibility = 17

View File

@@ -5,5 +5,5 @@ org.gradle.caching=true
org.gradle.daemon=true
# ????
pluginVersion=1.0.1-1.20.4
pluginVersion=2.0.0-1.20.4
mcVersion=1.20.4

View File

@@ -17,22 +17,41 @@ public class AdminCommands implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("playerblocklife.admin")) {
sender.sendMessage("§c你没有权限使用此命令");
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 (command.getName().equalsIgnoreCase("pblreload")) {
plugin.reloadConfig();
plugin.getBlockManager().loadData();
plugin.getSkinManager().loadAllSkins();
sender.sendMessage("§a插件配置已重载");
// 检查管理员权限和配置
if (!sender.hasPermission("playerblocklife.admin") || config.isAdminOnly(commandName)) {
String message = msgManager.getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
sender.sendMessage(message);
return true;
}
if (command.getName().equalsIgnoreCase("pbldelete")) {
if (commandName.equals("pblreload")) {
// 调用插件的完整重载方法
plugin.reloadPluginConfig();
String message = msgManager.getCommandMessage("pblreload", "success",
"&a配置已重载");
sender.sendMessage(message);
return true;
}
if (commandName.equals("pbldelete")) {
if (args.length < 1) {
sender.sendMessage("§c用法: /pbldelete <玩家名>");
String usage = msgManager.getCommandMessage("pbldelete", "usage",
"&c用法: /pbldelete <玩家>");
sender.sendMessage(usage);
return true;
}
@@ -44,59 +63,111 @@ public class AdminCommands implements CommandExecutor {
targetId = target.getUniqueId();
} else {
// 尝试从离线玩家获取UUID
targetId = Bukkit.getOfflinePlayer(targetName).getUniqueId();
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;
}
}
plugin.getBlockManager().clearPlayerBlocks(targetId);
sender.sendMessage("§a已删除玩家 " + targetName + " 的生命方块");
String message = msgManager.getCommandMessage("pbldelete", "success",
"&a已删除玩家 {player} 的生命方块!");
message = message.replace("{player}", targetName);
sender.sendMessage(message);
return true;
}
if (command.getName().equalsIgnoreCase("pblrevive")) {
if (commandName.equals("pblrevive")) {
Player target;
if (args.length < 1) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c控制台使用时必须指定玩家名: /pblrevive <玩家名>");
String usage = msgManager.getCommandMessage("pblrevive", "usage",
"&c用法: /pblrevive [玩家]");
sender.sendMessage(usage);
return true;
}
target = (Player) sender;
} else {
target = Bukkit.getPlayer(args[0]);
if (target == null) {
sender.sendMessage("§c玩家不存在或不在线");
String message = msgManager.getMessage("game.errors.player_offline",
"&c玩家 {player} 不在线!");
message = message.replace("{player}", args[0]);
sender.sendMessage(message);
return true;
}
}
plugin.getLifeSystem().revivePlayer(target);
sender.sendMessage("§a玩家 " + target.getName() + " 已复活!");
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);
} else {
sender.sendMessage("§c复活失败生命系统未初始化");
}
return true;
}
if (command.getName().equalsIgnoreCase("pblstats")) {
if (commandName.equals("pblstats")) {
if (plugin.getBlockManager() == null) {
sender.sendMessage("§c方块管理器未初始化");
return true;
}
int totalPlayers = plugin.getBlockManager().getPlayerBlocksCount();
int totalBlocks = plugin.getBlockManager().getTotalBlocksCount();
sender.sendMessage("§a===== PlayerBlockLife 统计 =====");
sender.sendMessage("§7注册玩家数: §e" + totalPlayers);
sender.sendMessage("§7总生命方块数: §e" + totalBlocks);
sender.sendMessage("§7在线玩家生命方块:");
// 获取统计标题
String title = msgManager.getCommandMessage("pblstats", "title",
"&6=== PlayerBlockLife 统计 ===");
sender.sendMessage(title);
// 在线玩家统计
String onlineMsg = msgManager.getCommandMessage("pblstats", "online_players",
"&e在线玩家: {count}");
onlineMsg = onlineMsg.replace("{count}", String.valueOf(Bukkit.getOnlinePlayers().size()));
sender.sendMessage(onlineMsg);
// 总方块统计
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++;
}
}
String eliminatedMsg = msgManager.getCommandMessage("pblstats", "eliminated_players",
"&e已淘汰玩家: {count}");
eliminatedMsg = eliminatedMsg.replace("{count}", String.valueOf(eliminatedCount));
sender.sendMessage(eliminatedMsg);
sender.sendMessage("§7在线玩家详情:");
for (Player player : Bukkit.getOnlinePlayers()) {
int blocks = plugin.getBlockManager().getRemainingBlocks(player.getUniqueId());
sender.sendMessage("§7- " + player.getName() + ": §e" + blocks + " §7/ §a5");
String status = blocks > 0 ? "§a存活" : "§c已淘汰";
sender.sendMessage("§7- " + player.getName() + ": §e" + blocks + " §7/ §a5 §7(" + status + "§7)");
}
sender.sendMessage("§a=================================");
return true;
}
sender.sendMessage("§c未知的管理员命令");
sender.sendMessage("§e可用命令:");
sender.sendMessage("§7/pblreload §8- §f重载插件配置");
sender.sendMessage("§7/pbldelete <玩家> §8- §f删除玩家的生命方块");
sender.sendMessage("§7/pblrevive [玩家] §8- §f复活被淘汰的玩家");
sender.sendMessage("§7/pblstats §8- §f查看插件统计");
String unknownMsg = msgManager.getMessage("game.errors.invalid_arguments",
"&c未知的管理员命令");
sender.sendMessage(unknownMsg);
return true;
}
}

View File

@@ -12,6 +12,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class BlockBreakListener implements Listener {
@@ -39,7 +41,9 @@ public class BlockBreakListener implements Listener {
if (ownerId.equals(breaker.getUniqueId())) {
if (breaker.getGameMode() != GameMode.CREATIVE) {
breaker.sendMessage("§c你不能挖掘自己的生命方块");
String message = plugin.getMessageManager().getMessage("game.errors.cannot_break_own_block",
"&c你不能挖掘自己的生命方块");
breaker.sendMessage(message);
event.setCancelled(true);
}
return;
@@ -47,19 +51,61 @@ public class BlockBreakListener implements Listener {
if (plugin.getBlockManager().removeBlock(location, breaker)) {
int remaining = plugin.getBlockManager().getRemainingBlocks(ownerId);
Player owner = Bukkit.getPlayer(ownerId);
String ownerName = owner != null ? owner.getName() : Bukkit.getOfflinePlayer(ownerId).getName();
if (remaining <= 0) {
plugin.getLifeSystem().handlePlayerDeath(ownerId);
}
breaker.sendMessage("§a✓ 成功破坏一个生命方块!");
breaker.sendMessage("§7剩余方块: §e" + remaining);
// 通知破坏者
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()));
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);
if (remaining == 1) {
breaker.sendMessage("§6⚡ 对方只剩最后1个生命方块了");
String lastBlockMsg = plugin.getMessageManager().getMessage("game.block.last_block_warning",
"&6⚡ 对方只剩最后1个生命方块了");
breaker.sendMessage(lastBlockMsg);
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);
if (remaining == 0) {
String allDestroyedMsg = plugin.getMessageManager().getMessage("game.block.all_destroyed",
"&c☠ 你的所有生命方块已被破坏!你已被淘汰!");
owner.sendMessage(allDestroyedMsg);
}
}
// 广播(如果启用)
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);
}
}
}
}
}
@@ -73,7 +119,9 @@ public class BlockBreakListener implements Listener {
UUID ownerId = plugin.getBlockManager().getBlockOwner(block.getLocation());
if (ownerId != null && !ownerId.equals(player.getUniqueId())) {
player.sendMessage("§c你不能在这里放置方块这是别人的生命方块区域");
String message = plugin.getMessageManager().getMessage("game.errors.cannot_place_in_block_area",
"&c你不能在这里放置方块这是别人的生命方块区域");
player.sendMessage(message);
event.setCancelled(true);
}
}

View File

@@ -19,49 +19,81 @@ public class CheckLifeBlocksCommand implements CommandExecutor {
@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)) {
sender.sendMessage("§c只有玩家可以使用此命令");
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c只有玩家可以使用此命令");
sender.sendMessage(message);
return true;
}
Player player = (Player) sender;
UUID playerId = player.getUniqueId();
List<Location> blocks = plugin.getBlockManager().getPlayerBlocks(playerId);
int remaining = blocks.size();
if (remaining == 0) {
player.sendMessage("§c你还没有设置生命方块");
player.sendMessage("§7使用 §e/setlifeblocks §7来设置你的生命方块");
// 检查是否允许玩家自己使用
if (!plugin.getConfigManager().isSelfUseAllowed("checklifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
player.sendMessage(message);
return true;
}
player.sendMessage("§a========== 你的生命方块信息 ==========");
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("§7剩余方块数量: §e" + remaining + " §7/ §a5");
player.sendMessage("§7当前生命值: §c" +
(plugin.getLifeSystem().getPlayerHealth(playerId) != null ?
plugin.getLifeSystem().getPlayerHealth(playerId) : "20") + "");
// 显示生命值(如果启用)
if (plugin.getConfigManager().isHealthSystemEnabled()) {
Integer health = plugin.getLifeSystem().getPlayerHealth(playerId);
player.sendMessage("§7当前生命值: §c" + (health != null ? health : "20") + "");
}
if (remaining <= 2) {
player.sendMessage("§c⚠ 警告!生命方块即将耗尽!");
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() : "未知世界";
player.sendMessage("§7" + (i + 1) + ". §e世界: " + worldName +
" §7坐标: §a" + loc.getBlockX() + " §7, §a" +
loc.getBlockY() + " §7, §a" + loc.getBlockZ());
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);
}
player.sendMessage("§a======================================");
if (remaining > 0) {
player.sendMessage("§e提示");
player.sendMessage("§7- 方块距离你: §a" +
(int) player.getLocation().distance(blocks.get(0)) + " §7格");
player.sendMessage("§7- 使用指南针可以追踪方块位置");
Location nearestBlock = plugin.getBlockManager().getNearestBlock(player);
if (nearestBlock != null) {
double distance = player.getLocation().distance(nearestBlock);
player.sendMessage("§7最近方块距离: §a" + (int)distance + " §7格");
}
}
return true;

View File

@@ -1,60 +1,291 @@
package com.playerblocklife;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class ConfigManager {
private final PlayerBlockLife plugin;
private FileConfiguration config;
private File configFile;
public ConfigManager(PlayerBlockLife plugin) {
this.plugin = plugin;
loadConfig();
this.configFile = new File(plugin.getDataFolder(), "config.yml");
}
/**
* 加载配置
*/
public void loadConfig() {
plugin.saveDefaultConfig();
config = plugin.getConfig();
// 确保配置文件夹存在
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdirs();
}
// 如果配置文件不存在从JAR中复制默认配置
if (!configFile.exists()) {
plugin.saveDefaultConfig();
plugin.logInfo("创建默认配置文件");
}
// 重新加载配置
reloadConfig();
}
/**
* 重新加载配置
*/
public void reloadConfig() {
plugin.reloadConfig();
config = plugin.getConfig();
// 重新从磁盘加载配置
config = YamlConfiguration.loadConfiguration(configFile);
// 加载默认配置作为后备
InputStream defaultConfigStream = plugin.getResource("config.yml");
if (defaultConfigStream != null) {
YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defaultConfigStream, StandardCharsets.UTF_8));
config.setDefaults(defaultConfig);
}
// 检查配置版本,如果需要则更新
checkConfigVersion();
plugin.logInfo("配置已加载");
}
/**
* 检查配置版本并更新
*/
private void checkConfigVersion() {
int currentVersion = config.getInt("config-version", 1);
int latestVersion = 1; // 最新配置版本
if (currentVersion < latestVersion) {
plugin.logWarning("检测到旧版配置文件,正在更新...");
updateConfig(currentVersion, latestVersion);
}
}
/**
* 更新配置文件
*/
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);
config.set("commands.setlifeblocks.allow_self_use", true);
config.set("commands.setlifeblocks.allow_admin_use", true);
config.set("commands.checklifeblocks.enabled", true);
config.set("commands.checklifeblocks.allow_self_use", true);
config.set("commands.checklifeblocks.allow_admin_use", true);
config.set("commands.pblreload.enabled", true);
config.set("commands.pblreload.admin_only", true);
config.set("commands.pbldelete.enabled", true);
config.set("commands.pbldelete.admin_only", true);
config.set("commands.pblrevive.enabled", true);
config.set("commands.pblrevive.admin_only", true);
config.set("commands.pblstats.enabled", true);
config.set("commands.pblstats.admin_only", true);
}
// 更新消息配置
if (!config.contains("messages.use_external_file")) {
config.set("messages.use_external_file", true);
config.set("messages.external_file", "messages.yml");
}
// 更新版本号
config.set("config-version", toVersion);
try {
config.save(configFile);
plugin.logInfo("配置文件已更新到版本 " + toVersion);
} catch (IOException e) {
plugin.logError("保存更新后的配置文件失败", e);
}
}
}
/**
* 保存配置
*/
public void saveConfig() {
try {
config.save(configFile);
} catch (IOException e) {
plugin.logError("保存配置文件失败", e);
}
}
/**
* 获取配置对象
*/
public FileConfiguration getConfig() {
if (config == null) {
reloadConfig();
}
return config;
}
// 以下为配置项的获取方法
public int getBlocksPerPlayer() {
return config.getInt("blocks.amount", 5);
return getConfig().getInt("blocks.amount", 5);
}
public int getSpreadRange() {
return config.getInt("blocks.spread", 5);
return getConfig().getInt("blocks.spread", 5);
}
public int getDepth() {
return config.getInt("blocks.depth", -1);
return getConfig().getInt("blocks.depth", -1);
}
public String getBlockMaterial() {
return getConfig().getString("blocks.material", "player_head");
}
public boolean isDieWhenBlocksGone() {
return config.getBoolean("game.die_when_blocks_gone", true);
return getConfig().getBoolean("game.die_when_blocks_gone", true);
}
public boolean isBecomeSpectator() {
return config.getBoolean("game.become_spectator", true);
return getConfig().getBoolean("game.become_spectator", true);
}
public boolean isHealthSystemEnabled() {
return config.getBoolean("game.health_system", true);
return getConfig().getBoolean("game.health_system", true);
}
public boolean isSkinSystemEnabled() {
return config.getBoolean("skin.enabled", true);
return getConfig().getBoolean("skin.enabled", true);
}
public String getSkinSource() {
return config.getString("skin.source", "player_profile");
return getConfig().getString("skin.source", "player_profile");
}
public int getCacheExpireDays() {
return getConfig().getInt("skin.cache.expire_days", 7);
}
public boolean isAutoSaveEnabled() {
return getConfig().getBoolean("storage.auto_save.enabled", true);
}
public int getAutoSaveInterval() {
return getConfig().getInt("storage.auto_save.interval", 300);
}
public String getStorageType() {
return getConfig().getString("storage.type", "yaml");
}
public boolean isBroadcastOnBlockBreak() {
return getConfig().getBoolean("game.broadcast.on_block_break", true);
}
public boolean isBroadcastOnPlayerDeath() {
return getConfig().getBoolean("game.broadcast.on_player_death", true);
}
public int getBroadcastRange() {
return getConfig().getInt("game.broadcast.range", 30);
}
public boolean isGiveExpReward() {
return getConfig().getBoolean("game.break_rewards.give_exp", true);
}
public int getExpRewardAmount() {
return getConfig().getInt("game.break_rewards.exp_amount", 5);
}
public boolean isProtectFromExplosions() {
return getConfig().getBoolean("protection.protect_from_explosions", true);
}
public boolean isProtectFromFire() {
return getConfig().getBoolean("protection.protect_from_fire", true);
}
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");
}
// 命令启用配置获取方法
public boolean isCommandEnabled(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".enabled", true);
}
public boolean isSelfUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_self_use", true);
}
public boolean isAdminUseAllowed(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".allow_admin_use", true);
}
public boolean isAdminOnly(String commandName) {
return getConfig().getBoolean("commands." + commandName + ".admin_only", false);
}
// 消息文件配置获取方法
public boolean useExternalMessageFile() {
return getConfig().getBoolean("messages.use_external_file", true);
}
public String getExternalMessageFileName() {
return getConfig().getString("messages.external_file", "messages.yml");
}
public String getMessage(String path, String defaultValue) {
String message = config.getString("messages." + path, defaultValue);
// 优先从外部消息文件获取
if (useExternalMessageFile()) {
// 这里应该调用MessageManager来获取消息
// 暂时返回默认值MessageManager会处理具体逻辑
return defaultValue;
}
// 从config.yml获取
String message = getConfig().getString("messages." + path, defaultValue);
if (message != null) {
message = message.replace("&", "§");
}

View File

@@ -0,0 +1,183 @@
package com.playerblocklife;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class MessageManager {
private final PlayerBlockLife plugin;
private FileConfiguration messageConfig;
private File messageFile;
private final Map<String, String> messageCache = new HashMap<>();
public MessageManager(PlayerBlockLife plugin) {
this.plugin = plugin;
this.messageFile = new File(plugin.getDataFolder(), "messages.yml");
}
/**
* 加载消息配置
*/
public void loadMessages() {
ConfigManager config = plugin.getConfigManager();
// 检查是否使用外部消息文件
if (config.useExternalMessageFile()) {
String fileName = config.getExternalMessageFileName();
this.messageFile = new File(plugin.getDataFolder(), fileName);
// 确保配置文件夹存在
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdirs();
}
// 如果消息文件不存在从JAR中复制默认配置
if (!messageFile.exists()) {
plugin.saveResource(fileName, false);
plugin.logInfo("创建默认消息配置文件: " + fileName);
}
// 加载消息配置
reloadMessages();
} else {
// 使用config.yml中的消息
plugin.logInfo("使用config.yml中的消息配置");
messageConfig = null;
messageCache.clear();
}
}
/**
* 重新加载消息配置
*/
public void reloadMessages() {
ConfigManager config = plugin.getConfigManager();
if (!config.useExternalMessageFile()) {
return;
}
try {
messageConfig = YamlConfiguration.loadConfiguration(messageFile);
// 加载默认消息作为后备
InputStream defaultStream = plugin.getResource(messageFile.getName());
if (defaultStream != null) {
YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defaultStream, StandardCharsets.UTF_8));
messageConfig.setDefaults(defaultConfig);
}
// 清空缓存
messageCache.clear();
plugin.logInfo("消息配置已加载: " + messageFile.getName());
} catch (Exception e) {
plugin.logError("加载消息配置文件失败", e);
}
}
/**
* 获取消息
*/
public String getMessage(String path, String defaultValue) {
ConfigManager config = plugin.getConfigManager();
// 检查缓存
String cacheKey = path + "|" + defaultValue;
if (messageCache.containsKey(cacheKey)) {
return messageCache.get(cacheKey);
}
String message;
if (config.useExternalMessageFile() && messageConfig != null) {
// 从外部消息文件获取
message = messageConfig.getString(path, defaultValue);
if (message == null) {
message = defaultValue;
}
} else {
// 从config.yml获取或使用默认值
message = config.getMessage(path, defaultValue);
}
// 替换颜色代码
if (message != null) {
message = message.replace("&", "§");
}
// 缓存结果
messageCache.put(cacheKey, message);
return message;
}
/**
* 获取格式化消息(替换变量)
*/
public String getFormattedMessage(String path, String defaultValue, Map<String, String> variables) {
String message = getMessage(path, defaultValue);
if (message != null && variables != null) {
for (Map.Entry<String, String> entry : variables.entrySet()) {
message = message.replace("{" + entry.getKey() + "}", entry.getValue());
}
}
return message;
}
/**
* 获取控制台消息
*/
public String getConsoleMessage(String path, String defaultValue) {
String message = getMessage("console." + path, defaultValue);
// 移除颜色代码(控制台不需要)
if (message != null) {
message = message.replace("§", "&");
}
return message;
}
/**
* 获取游戏内消息
*/
public String getGameMessage(String path, String defaultValue) {
return getMessage("game." + path, defaultValue);
}
/**
* 获取命令消息
*/
public String getCommandMessage(String command, String path, String defaultValue) {
return getMessage("commands." + command + "." + path, defaultValue);
}
/**
* 获取广播消息
*/
public String getBroadcastMessage(String path, String defaultValue) {
return getMessage("broadcast." + path, defaultValue);
}
/**
* 检查消息文件是否存在
*/
public boolean hasExternalMessageFile() {
return messageFile.exists();
}
/**
* 获取消息文件路径
*/
public String getMessageFilePath() {
return messageFile.getAbsolutePath();
}
}

View File

@@ -1,6 +1,8 @@
package com.playerblocklife;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level;
public class PlayerBlockLife extends JavaPlugin {
@@ -9,37 +11,44 @@ public class PlayerBlockLife extends JavaPlugin {
private SkinManager skinManager;
private LifeSystem lifeSystem;
private ConfigManager configManager;
private MessageManager messageManager;
// 在 PlayerBlockLife.java 中添加:
@Override
public void reloadConfig() {
super.reloadConfig();
configManager.reloadConfig();
getLogger().info("配置已重新加载");
}
@Override
public void onEnable() {
instance = this;
// 第一步:保存默认配置
saveDefaultConfig();
// 第二步:初始化管理器(注意顺序!)
this.configManager = new ConfigManager(this);
this.messageManager = new MessageManager(this);
this.skinManager = new SkinManager(this);
this.blockManager = new PlayerBlockManager(this, skinManager);
this.lifeSystem = new LifeSystem(this);
// 第三步:加载数据(必须在管理器初始化之后)
this.configManager.loadConfig();
this.messageManager.loadMessages();
// 第四步:注册事件监听器
getServer().getPluginManager().registerEvents(new BlockBreakListener(this), this);
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
getServer().getPluginManager().registerEvents(new PlayerQuitListener(this), this);
// 第五步:注册命令
getCommand("setlifeblocks").setExecutor(new SetLifeBlocksCommand(this));
getCommand("checklifeblocks").setExecutor(new CheckLifeBlocksCommand(this));
getCommand("pblreload").setExecutor(new AdminCommands(this));
getCommand("pbldelete").setExecutor(new AdminCommands(this));
getCommand("pblrevive").setExecutor(new AdminCommands(this));
getCommand("pblstats").setExecutor(new AdminCommands(this));
// 第六步:加载其他数据
blockManager.loadData();
skinManager.loadAllSkins();
// 第七步:启动定时任务
startScheduler();
getLogger().info("§a========================================");
@@ -50,6 +59,7 @@ public class PlayerBlockLife extends JavaPlugin {
@Override
public void onDisable() {
// 保存数据
if (blockManager != null) {
blockManager.saveData();
}
@@ -59,15 +69,60 @@ public class PlayerBlockLife extends JavaPlugin {
getLogger().info("§cPlayerBlockLife 插件已禁用");
}
/**
* 重写 reloadConfig 方法,避免循环依赖
*/
@Override
public void reloadConfig() {
// 只调用父类的reloadConfig不调用configManager的方法
super.reloadConfig();
getLogger().info("基础配置文件已重新加载");
}
/**
* 插件的完整重载方法(用于命令)
*/
public void reloadPluginConfig() {
if (configManager != null) {
configManager.reloadConfig();
}
if (messageManager != null) {
messageManager.reloadMessages();
}
if (blockManager != null) {
blockManager.loadData();
}
if (skinManager != null) {
skinManager.loadAllSkins();
}
getLogger().info("插件配置已完全重载");
}
private void startScheduler() {
// 每5分钟自动保存数据
getServer().getScheduler().runTaskTimerAsynchronously(this, () -> {
blockManager.saveData();
skinManager.saveSkinData();
if (blockManager != null) {
blockManager.saveData();
}
if (skinManager != null) {
skinManager.saveSkinData();
}
getLogger().info("数据已自动保存");
}, 6000L, 6000L);
// 每10秒检查玩家生命方块
getServer().getScheduler().runTaskTimer(this, () -> {
lifeSystem.checkAllPlayers();
if (lifeSystem != null) {
lifeSystem.checkAllPlayers();
}
}, 200L, 200L);
// 每分钟清理一次过期缓存
getServer().getScheduler().runTaskTimerAsynchronously(this, () -> {
if (skinManager != null) {
skinManager.cleanupOldCache();
}
}, 1200L, 1200L);
}
public static PlayerBlockLife getInstance() {
@@ -90,6 +145,10 @@ public class PlayerBlockLife extends JavaPlugin {
return configManager;
}
public MessageManager getMessageManager() {
return messageManager;
}
public void logInfo(String message) {
getLogger().info(message);
}

View File

@@ -34,32 +34,43 @@ 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();
return generateLifeBlocksForPlayer(player, blockAmount, spreadRange, requireOpenSky, maxAttempts);
}
/**
* 为玩家生成生命方块(新方法,支持自动生成)
*/
public boolean generateLifeBlocksForPlayer(Player player, int blockAmount, int spreadRange, boolean requireOpenSky, int maxAttempts) {
UUID playerId = player.getUniqueId();
String playerName = player.getName();
// 检查是否已有生命方块
if (hasLifeBlocks(playerId)) {
player.sendMessage("§c你已经有生命方块了");
player.sendMessage("§e使用 /checklifeblocks 查看位置");
return false;
}
// 检查玩家皮肤是否已加载
if (!skinManager.isSkinLoaded(playerId)) {
player.sendMessage("§e你的皮肤正在加载中请稍候...");
player.sendMessage("§7(如果长时间未加载完成,请重新加入服务器)");
return false;
}
List<Location> blocks = new ArrayList<>();
int blocksPlaced = 0;
int attempts = 0;
// 尝试在中心周围生成5个方块
for (int attempt = 0; attempt < 20 && blocksPlaced < 5; attempt++) {
Location blockLoc = findSuitableLocation(center);
// 尝试生成指定数量的方块
while (blocksPlaced < blockAmount && attempts < maxAttempts) {
Location blockLoc = findSurfaceLocation(player.getLocation(), spreadRange, requireOpenSky);
attempts++;
if (blockLoc != null && placePlayerHead(blockLoc, playerId, playerName)) {
blocks.add(blockLoc);
@@ -74,22 +85,70 @@ public class PlayerBlockManager {
if (blocksPlaced > 0) {
playerBlocks.put(playerId, blocks);
saveData();
player.sendMessage("§a========================================");
player.sendMessage("§a成功生成 §e" + blocksPlaced + " §a个生命方块");
player.sendMessage("§6方块使用了你的皮肤头像");
player.sendMessage("§c⚠ 警告: 方块被挖光时,你将死亡!");
player.sendMessage("§7使用 /checklifeblocks 查看方块位置");
player.sendMessage("§a========================================");
return true;
} else {
player.sendMessage("§c无法生成生命方块");
player.sendMessage("§7请确保周围有足够的空间至少5个可放置位置");
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;
}
/**
* 寻找合适的位置
*/

View File

@@ -9,6 +9,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PlayerJoinListener implements Listener {
@@ -26,34 +28,140 @@ public class PlayerJoinListener implements Listener {
// 异步加载玩家皮肤
plugin.getSkinManager().loadPlayerSkinAsync(player);
// 延迟发送消息,确保皮肤加载完成
// 延迟执行,确保皮肤加载完成
Bukkit.getScheduler().runTaskLater(plugin, () -> {
int remainingBlocks = plugin.getBlockManager().getRemainingBlocks(playerId);
if (remainingBlocks > 0) {
// 检查是否启用自动生成
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("§e欢迎回来§6" + player.getName() + "§e");
player.sendMessage("§7你还有 §e" + remainingBlocks + " §7个生命方块");
player.sendMessage("§7使用 §e/checklifeblocks §7查看方块位置");
player.sendMessage(welcomeMsg);
player.sendMessage(blocksMsg);
player.sendMessage(checkMsg);
player.sendMessage("§a========================================");
if (remainingBlocks <= 2) {
player.sendMessage("§c⚠ 警告!你的生命方块即将耗尽!");
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 {
player.sendMessage("§e欢迎加入游戏");
player.sendMessage("§7使用 §e/setlifeblocks §7来设置你的生命方块");
player.sendMessage("§6游戏规则");
player.sendMessage("§7- 每个玩家有5个生命方块");
player.sendMessage("§7- 方块被其他玩家挖光时,你将死亡");
player.sendMessage("§7- 方块使用你的皮肤作为材质");
player.sendMessage("§7- 你可以自由移动,但方块固定位置");
// 没有方块且自动生成未启用
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);
}
}, 40L);
}
/**
* 为玩家自动生成生命方块
*/
private void generateLifeBlocksForPlayer(Player player) {
try {
// 获取配置
ConfigManager config = plugin.getConfigManager();
int blockAmount = config.getBlocksPerPlayer();
int spreadRange = config.getSpreadRange();
boolean requireOpenSky = config.isRequireOpenSky();
int maxAttempts = config.getMaxAttempts();
// 调用方块管理器生成方块
boolean success = plugin.getBlockManager().generateLifeBlocksForPlayer(
player, blockAmount, spreadRange, requireOpenSky, maxAttempts
);
if (success) {
// 发送成功消息
String message = plugin.getMessageManager().getMessage("console.blocks_generated",
"&a[PlayerBlockLife] 已为玩家 {player} 生成 {amount} 个生命方块");
message = message.replace("{player}", player.getName())
.replace("{amount}", String.valueOf(blockAmount));
// 移除颜色代码用于日志
String logMessage = message.replace("&", "");
plugin.getLogger().info(logMessage);
// 发送给玩家
String playerMsg = plugin.getMessageManager().getMessage("game.block.placed",
"&a已为你生成 {amount} 个生命方块!");
playerMsg = playerMsg.replace("{amount}", String.valueOf(blockAmount))
.replace("&", "§");
player.sendMessage(playerMsg);
} else {
// 生成失败
String failureMsg = plugin.getMessageManager().getMessage("console.error_generating_blocks",
"&c[PlayerBlockLife] 为玩家 {player} 生成生命方块时失败");
failureMsg = failureMsg.replace("{player}", player.getName());
// 移除颜色代码用于日志
String logFailureMsg = failureMsg.replace("&", "");
plugin.getLogger().warning(logFailureMsg);
// 根据配置处理失败
String onFailure = config.getOnFailureAction();
if (onFailure.equals("notify")) {
String notifyMsg = plugin.getMessageManager().getMessage("game.errors.cannot_generate_blocks",
"&c无法生成生命方块找不到合适的位置");
notifyMsg = notifyMsg.replace("&", "§");
player.sendMessage(notifyMsg);
player.sendMessage("§7请手动使用 §e/setlifeblocks §7命令生成方块");
} else if (onFailure.equals("teleport_to_spawn")) {
player.teleport(player.getWorld().getSpawnLocation());
player.sendMessage("§e已将你传送至出生点请手动生成方块");
}
}
} catch (Exception e) {
plugin.getLogger().severe("为玩家 " + player.getName() + " 生成生命方块时发生错误: " + e.getMessage());
e.printStackTrace();
String errorMsg = plugin.getMessageManager().getMessage("console.error_generating_blocks",
"&c[PlayerBlockLife] 为玩家 {player} 生成生命方块时出错: {error}");
if (errorMsg != null) {
String errorDetail = e.getMessage() != null ? e.getMessage() : "未知错误";
errorMsg = errorMsg.replace("{player}", player.getName())
.replace("{error}", errorDetail)
.replace("&", "§");
plugin.getLogger().severe(errorMsg);
}
}
}
@EventHandler
public void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();

View File

@@ -18,8 +18,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)) {
sender.sendMessage("§c只有玩家可以使用此命令");
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c只有玩家可以使用此命令");
sender.sendMessage(message);
return true;
}
@@ -31,22 +41,42 @@ public class SetLifeBlocksCommand implements CommandExecutor {
return true;
}
if (args.length > 0 && player.hasPermission("playerblocklife.admin")) {
if (args[0].equalsIgnoreCase("other")) {
if (args.length < 2) {
player.sendMessage("§c用法: /setlifeblocks other <玩家名>");
return true;
}
Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
player.sendMessage("§c找不到玩家: " + args[1]);
return true;
}
setBlocksForPlayer(target, player);
// 检查管理员权限和配置
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;
}
// 检查是否允许玩家自己使用
if (!plugin.getConfigManager().isSelfUseAllowed("setlifeblocks")) {
String message = plugin.getMessageManager().getMessage("game.errors.no_permission",
"&c你没有权限使用此命令");
player.sendMessage(message);
return true;
}
setBlocksForPlayer(player, null);
@@ -55,21 +85,28 @@ public class SetLifeBlocksCommand implements CommandExecutor {
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())) {
executor.sendMessage("§c玩家 " + target.getName() + " 已经有生命方块了!");
String message = msgManager.getMessage("game.errors.player_already_has_blocks",
"&c玩家 {player} 已经有生命方块了!");
message = message.replace("{player}", target.getName());
executor.sendMessage(message);
} else {
target.sendMessage("§c你已经有生命方块了");
target.sendMessage("§e使用 /checklifeblocks 查看位置");
target.sendMessage("§e使用 /setlifeblocks reset 重置方块位置");
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())) {
executor.sendMessage("§e玩家 " + target.getName() + " 的皮肤正在加载中,请稍候...");
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(如果长时间未加载完成,请重新加入服务器)");
@@ -81,11 +118,23 @@ public class SetLifeBlocksCommand implements CommandExecutor {
boolean success = plugin.getBlockManager().setLifeBlocks(target, target.getLocation());
if (success) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
executor.sendMessage("§a已为玩家 " + target.getName() + " 生成生命方块!");
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())) {
executor.sendMessage("§c为玩家 " + target.getName() + " 生成生命方块失败!");
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生成失败请稍后再试或联系管理员");
}
@@ -99,11 +148,23 @@ public class SetLifeBlocksCommand implements CommandExecutor {
if (success) {
if (executor != null && !targetId.equals(executor.getUniqueId())) {
executor.sendMessage("§a已为玩家 " + target.getName() + " 生成生命方块!");
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())) {
executor.sendMessage("§c为玩家 " + target.getName() + " 生成生命方块失败!");
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生成失败请确保周围有足够空间");
}
@@ -111,22 +172,13 @@ public class SetLifeBlocksCommand implements CommandExecutor {
}
private void showHelp(Player player) {
player.sendMessage("§a========== PlayerBlockLife 帮助 ==========");
player.sendMessage("§e/setlifeblocks §7- 设置你的生命方块");
player.sendMessage("§e/checklifeblocks §7- 查看你的生命方块位置");
player.sendMessage("§e/setlifeblocks reset §7- 重置生命方块位置");
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- 显示此帮助");
if (player.hasPermission("playerblocklife.admin")) {
player.sendMessage("§6管理员命令:");
player.sendMessage("§e/setlifeblocks other <玩家> §7- 为其他玩家设置生命方块");
player.sendMessage("§e/pblreload §7- 重载插件配置");
player.sendMessage("§e/pbldelete <玩家> §7- 删除玩家的生命方块");
}
player.sendMessage("§7游戏规则:");
player.sendMessage("§7- 每个玩家有5个生命方块");
player.sendMessage("§7- 方块被挖光时,玩家死亡");
player.sendMessage("§7- 方块使用玩家的皮肤作为材质");
player.sendMessage("§a======================================");
player.sendMessage(helpMessage);
}
}

View File

@@ -1,5 +1,5 @@
# PlayerBlockLife 配置文件
# 版本: 1.0.1-1.20.4
config-version: 1
# 方块设置
blocks:
@@ -10,28 +10,7 @@ blocks:
# 方块埋藏深度0为地面负数为地下
depth: -1
# 方块材质类型
# 可选值: player_head, custom_block, default
material: player_head
# 是否在生成方块时自动填充周围的方块
fill_around: true
# 当使用玩家头颅时的设置
player_head:
# 是否随机旋转头颅方向
random_rotation: true
# 是否显示玩家名字
show_player_name: true
# 是否显示特殊效果
show_effects: true
# 当使用自定义方块时的设置
custom_block:
# 自定义方块材质
material: DIAMOND_BLOCK
# 是否发光
glowing: true
# 发光等级 (0-15)
light_level: 3
# 游戏规则
game:
@@ -39,59 +18,8 @@ game:
die_when_blocks_gone: true
# 死亡后是否变成观察者
become_spectator: true
# 观察者模式
spectator_mode:
# 是否可以飞行
can_fly: true
# 是否可以看到其他玩家
can_see_players: true
# 是否可以穿墙
can_clip: true
# 是否启用生命值系统
health_system: true
# 生命值计算公式
health_formula:
# 方块数量与生命值的关系
# 格式: 方块数量:生命值
5: 20
4: 16
3: 12
2: 8
1: 4
0: 0
# 状态效果
status_effects:
# 当剩余2个方块时
two_blocks_left:
- type: SLOW
amplifier: 1
duration: 100
# 当剩余1个方块时
one_block_left:
- type: SLOW
amplifier: 2
duration: 100
- type: WEAKNESS
amplifier: 0
duration: 100
- type: BLINDNESS
amplifier: 0
duration: 100
# 挖掘奖励
break_rewards:
# 是否给予经验
give_exp: true
# 经验数量
exp_amount: 5
# 是否给予物品
give_items: false
# 物品列表
items:
- DIAMOND:1
- GOLD_INGOT:3
# 广播设置
broadcast:
@@ -101,58 +29,29 @@ game:
range: 30
# 玩家死亡时是否全服广播
on_player_death: true
# 新玩家加入时是否广播
on_player_join: false
# 挖掘奖励
break_rewards:
# 是否给予经验
give_exp: true
# 经验数量
exp_amount: 5
# 皮肤系统
skin:
# 是否启用皮肤系统
enabled: true
# 皮肤来源
# 皮肤来源 (player_profile, local_cache)
source: player_profile
# 可选值: player_profile, mojang_api, local_cache
# player_profile: 从玩家本地缓存获取推荐不调用外部API
# mojang_api: 从Mojang API获取需要网络
# local_cache: 从本地缓存获取
# 缓存设置
cache:
# 是否启用缓存
enabled: true
# 缓存过期时间(天)
expire_days: 7
# 缓存最大大小MB
max_size_mb: 100
# 默认皮肤(当无法获取玩家皮肤时)
default_skin:
# 使用哪个玩家的皮肤作为默认
player_name: Steve
# 或使用自定义UUID
uuid: 8667ba71-b85a-4004-af54-457a9734eed7
# 是否随机分配默认皮肤
random_default: false
# 可选的默认皮肤列表
available_skins:
- Steve
- Alex
- Enderman
# 皮肤处理
processing:
# 是否异步处理皮肤
async: true
# 处理线程数
threads: 2
# 超时时间(秒)
timeout: 10
# 重试次数
retry_times: 3
# 数据存储
storage:
# 存储类型
# 可选值: yaml, json, sqlite, mysql
# 存储类型 (yaml, json, sqlite)
type: yaml
# 自动保存
@@ -162,221 +61,71 @@ storage:
# 保存间隔(秒)
interval: 300
# YAML存储设置
yaml:
# 数据文件编码
encoding: UTF-8
# 是否压缩
compress: false
# SQLite设置
sqlite:
# 数据库文件路径
file: plugins/PlayerBlockLife/data.db
# 连接池大小
pool_size: 5
# MySQL设置
mysql:
# 数据库主机
host: localhost
# 数据库端口
port: 3306
# 数据库名
database: minecraft
# 用户名
username: root
# 密码
password: password
# 表前缀
table_prefix: pbl_
# 连接池设置
pool:
max_connections: 10
min_connections: 2
connection_timeout: 30000
idle_timeout: 600000
# 消息配置
messages:
# 消息前缀
prefix: "&6[&ePlayerBlockLife&6]&r "
# 消息颜色
colors:
success: "&a"
error: "&c"
warning: "&e"
info: "&7"
highlight: "&6"
# 命令消息
commands:
setlifeblocks:
success: "&a已为你生成 {blocks} 个生命方块!"
already_has: "&c你已经有生命方块了使用 /checklifeblocks 查看位置"
no_skin: "&e你的皮肤正在加载中请稍候..."
failed: "&c无法生成生命方块请确保周围有足够空间"
checklifeblocks:
no_blocks: "&c你还没有设置生命方块使用 /setlifeblocks 来设置"
info: "&a你的生命方块信息"
location: "&7{index}. &e世界: {world} &7坐标: &a{x}&7, &a{y}&7, &a{z}"
remaining: "&7剩余方块: &e{remaining}&7/&a5"
health: "&7当前生命值: &c{health} ❤"
admin:
reload: "&a插件配置已重载"
delete_success: "&a已删除玩家 {player} 的生命方块"
delete_failed: "&c删除失败玩家不存在或没有生命方块"
revive_success: "&a玩家 {player} 已复活!"
revive_failed: "&c复活失败玩家不存在或未死亡"
# 游戏消息
game:
block_destroyed:
owner: "&c⚠ 警告!你的生命方块被 {breaker} 破坏了!"
breaker: "&a你破坏了 {owner} 的生命方块!"
remaining: "&7对方剩余生命方块: &a{remaining}"
broadcast: "&7[附近] &e一个生命方块被破坏了"
death:
title: "&4☠ 你死了!"
subtitle: "&c所有生命方块已被挖光"
broadcast: "&4☠ 玩家 {player} 的生命方块已被全部挖光,惨遭淘汰!"
spectator: "&e你已被淘汰可以观察其他玩家。等待下一轮游戏开始..."
warning:
low_blocks: "&c⚠ 警告!生命方块即将耗尽!"
last_block: "&4⚠ 警告!这是最后一个生命方块!"
health_low: "&4⚠ 警告!生命值过低!"
# 加入消息
join:
welcome: "&e欢迎加入游戏"
welcome_back: "&e欢迎回来{player}"
rules: |
&6游戏规则
&7- 每个玩家有5个生命方块
&7- 方块被其他玩家挖光时,你将死亡
&7- 方块使用你的皮肤作为材质
&7- 你可以自由移动,但方块固定位置
remaining_blocks: "&7你还有 {blocks} 个生命方块"
commands: "&7使用 &e/setlifeblocks &7来设置你的生命方块"
# 效果设置
effects:
# 方块放置效果
place:
particles:
- type: ENCHANTMENT_TABLE
count: 30
offset_x: 0.3
offset_y: 0.3
offset_z: 0.3
speed: 0.1
sounds:
- type: BLOCK_ANVIL_PLACE
volume: 0.5
pitch: 1.2
# 方块破坏效果
break:
particles:
- type: BLOCK_CRACK
count: 50
offset_x: 0.5
offset_y: 0.5
offset_z: 0.5
speed: 0.5
data: PLAYER_HEAD
- type: SMOKE_LARGE
count: 20
offset_x: 0.3
offset_y: 0.3
offset_z: 0.3
speed: 0.05
sounds:
- type: ENTITY_ITEM_BREAK
volume: 1.0
pitch: 0.8
- type: BLOCK_GLASS_BREAK
volume: 0.8
pitch: 1.0
knockback:
enabled: true
power: 0.5
vertical: 0.3
# 玩家受伤效果
damage:
particles:
- type: DAMAGE_INDICATOR
count: 10
sounds:
- type: ENTITY_PLAYER_HURT
volume: 1.0
pitch: 1.0
# 玩家死亡效果
player_death:
particles:
- type: EXPLOSION_HUGE
count: 5
sounds:
- type: ENTITY_WITHER_DEATH
volume: 0.7
pitch: 0.8
- type: ENTITY_LIGHTNING_BOLT_THUNDER
volume: 1.0
pitch: 0.5
# 保护设置
protection:
# 是否保护生命方块不被非玩家破坏
protect_from_non_players: true
# 是否保护生命方块不被爆炸破坏
protect_from_explosions: true
# 是否保护生命方块不被火焰烧毁
protect_from_fire: true
# 是否保护生命方块不被活塞推动
protect_from_pistons: true
# 是否允许TNT破坏生命方块
allow_tnt_damage: false
# 是否允许苦力怕爆炸破坏生命方块
allow_creeper_explosions: false
# 是否允许末影龙破坏生命方块
allow_ender_dragon_damage: false
# 是否允许其他插件修改生命方块
allow_plugin_modification: false
# 世界保护
world_guard:
# 是否与WorldGuard集成
enabled: false
# 需要保护的地区标志
region_flags:
- block-break
- block-place
- pvp
# 白名单地区(允许破坏生命方块的地区)
whitelist_regions: []
# 黑名单地区(不允许破坏生命方块的地区)
blacklist_regions: []
# 自动生成设置
auto-generation:
# 玩家加入时是否自动生成生命方块
enabled: true
# 生成位置要求:上方无方块覆盖的地表
require_open_sky: true
# 最大尝试次数(如果找不到合适位置)
max_attempts: 50
# 生成失败时的处理方式 (none, notify, teleport_to_spawn)
on_failure: notify
# 调试设
debug:
# 是否启用调试模式
enabled: false
# 调试级别
# 可选值: INFO, WARNING, ERROR, DEBUG
level: INFO
# 是否记录到文件
log_to_file: true
# 日志文件路径
log_file: plugins/PlayerBlockLife/debug.log
# 是否显示详细事件日志
verbose_events: false
# 是否记录性能数据
log_performance: false
# 性能日志间隔(秒)
performance_log_interval: 60
# 命令启用配
commands:
# setlifeblocks 命令
setlifeblocks:
enabled: true
# 是否允许玩家自己使用
allow_self_use: true
# 是否允许管理员为其他玩家设置
allow_admin_use: true
# checklifeblocks 命令
checklifeblocks:
enabled: true
# 是否允许玩家自己查看
allow_self_use: true
# 是否允许管理员查看其他玩家
allow_admin_use: true
# pblreload 命令
pblreload:
enabled: true
# 仅限管理员使用
admin_only: true
# pbldelete 命令
pbldelete:
enabled: true
# 仅限管理员使用
admin_only: true
# pblrevive 命令
pblrevive:
enabled: true
# 仅限管理员使用
admin_only: true
# pblstats 命令
pblstats:
enabled: true
# 仅限管理员使用
admin_only: true
# 消息配置(现在使用独立的 messages.yml 文件)
messages:
# 是否启用独立的消息文件
use_external_file: true
# 外部消息文件名称
external_file: "messages.yml"

View File

@@ -0,0 +1,108 @@
# PlayerBlockLife 消息配置文件
# 所有插件输出消息都可以在这里自定义
# 控制台消息
console:
plugin_enabled: "&a[PlayerBlockLife] 插件已启用!版本: {version}"
plugin_disabled: "&c[PlayerBlockLife] 插件已禁用!"
config_reloaded: "&a[PlayerBlockLife] 配置已重载!"
player_joined: "&a[PlayerBlockLife] 玩家 {player} 已加入,正在生成生命方块..."
blocks_generated: "&a[PlayerBlockLife] 已为玩家 {player} 生成 {amount} 个生命方块"
player_eliminated: "&c[PlayerBlockLife] 玩家 {player} 的生命方块已被挖光,已被淘汰!"
error_generating_blocks: "&c[PlayerBlockLife] 为玩家 {player} 生成生命方块时出错: {error}"
# 游戏内消息
game:
# 方块相关
block:
destroyed:
owner: "&c⚠ 警告!你的生命方块被 {breaker} 破坏了!剩余: {remaining}/{total}"
breaker: "&a你破坏了 {owner} 的生命方块!"
all_destroyed: "&c☠ 你的所有生命方块已被破坏!你已被淘汰!"
placed: "&a已为你生成 {amount} 个生命方块!"
check_location: "&e你的生命方块位置"
location_item: "&7- {world} ({x}, {y}, {z})"
no_blocks: "&c你还没有生命方块"
# 玩家状态
player:
eliminated: "&c玩家 {player} 已被淘汰!"
revived: "&a玩家 {player} 已被复活!"
already_eliminated: "&c玩家 {player} 已被淘汰,无法执行此操作!"
not_eliminated: "&c玩家 {player} 未被淘汰!"
# 错误消息
errors:
no_permission: "&c你没有权限使用此命令"
player_not_found: "&c玩家 {player} 未找到!"
player_offline: "&c玩家 {player} 不在线!"
invalid_arguments: "&c参数无效用法: {usage}"
command_disabled: "&c此命令已被禁用"
world_not_found: "&c世界 {world} 未找到!"
cannot_generate_blocks: "&c无法生成生命方块{reason}"
internal_error: "&c发生内部错误请联系管理员"
# 成功消息
success:
blocks_reset: "&a已重置你的生命方块"
blocks_deleted: "&a已删除玩家 {player} 的生命方块!"
config_reloaded: "&a配置已重载"
player_revived: "&a玩家 {player} 已复活!"
# 命令消息
commands:
setlifeblocks:
success: "&a已为你生成 {blocks} 个生命方块!"
already_has: "&c你已经有生命方块了使用 /checklifeblocks 查看位置"
help: |
&6=== PlayerBlockLife 帮助 ===
&e/setlifeblocks &7- 设置你的生命方块
&e/setlifeblocks reset &7- 重置生命方块位置
&e/setlifeblocks other <玩家> &7- 为其他玩家设置(管理员)
&e/setlifeblocks help &7- 显示此帮助
usage: "&c用法: /setlifeblocks [reset|other|help]"
checklifeblocks:
success: "&e你的生命方块位置"
no_blocks: "&c你还没有生命方块"
usage: "&c用法: /checklifeblocks"
pblreload:
success: "&a配置已重载"
usage: "&c用法: /pblreload"
pbldelete:
success: "&a已删除玩家 {player} 的生命方块!"
usage: "&c用法: /pbldelete <玩家>"
pblrevive:
success: "&a玩家 {player} 已复活!"
usage: "&c用法: /pblrevive [玩家]"
pblstats:
title: "&6=== PlayerBlockLife 统计 ==="
online_players: "&e在线玩家: {count}"
total_blocks: "&e总生命方块: {count}"
eliminated_players: "&e已淘汰玩家: {count}"
usage: "&c用法: /pblstats"
# 广播消息
broadcast:
block_destroyed: "&6{breaker} &7破坏了 &c{owner} &7的生命方块"
player_eliminated: "&c☠ {player} &7的生命方块已被挖光已被淘汰"
player_revived: "&a✨ {player} &7已被复活"
# 变量说明
# {player} - 玩家名称
# {breaker} - 破坏者名称
# {owner} - 方块所有者名称
# {amount} - 方块数量
# {remaining} - 剩余方块数量
# {total} - 总方块数量
# {x}, {y}, {z} - 坐标
# {world} - 世界名称
# {version} - 插件版本
# {error} - 错误信息
# {reason} - 原因
# {count} - 计数
# {usage} - 命令用法

View File

@@ -1,41 +1,75 @@
name: PlayerBlockLife
version: 1.0.1-1.20.4
version: 2.0.0-1.20.4
main: com.playerblocklife.PlayerBlockLife
api-version: 1.20
author: xiaobai
description: 玩家生命方块系统 - 方块被挖光则死亡
website: https://github.com/yourname/PlayerBlockLife
prefix: PBL
commands:
setlifeblocks:
description: 设置你的生命方块(使用你的皮肤)
usage: /<command>
usage: |
/<command> - 设置你的生命方块
/<command> help - 显示帮助
/<command> reset - 重置生命方块位置
/<command> other <玩家> - 为其他玩家设置(管理员)
aliases: [sbl, lifeblocks, setblocks]
permission: playerblocklife.set
permission-message: "§c你没有权限使用此命令"
checklifeblocks:
description: 查看你的生命方块位置
usage: /<command>
aliases: [cbl, checklife, myblocks]
permission: playerblocklife.check
permission-message: "§c你没有权限使用此命令"
pblreload:
description: 重载插件配置
usage: /<command>
aliases: [pblr, pblreload]
aliases: [pblr]
permission: playerblocklife.admin
permission-message: "§c你没有权限使用此命令"
pbldelete:
description: 删除指定玩家的生命方块
usage: /<command> <玩家>
aliases: [pbldel, deleteblocks]
permission: playerblocklife.admin
permission-message: "§c你没有权限使用此命令"
pblrevive:
description: 复活被淘汰的玩家
usage: /<command> [玩家]
aliases: [revive]
permission: playerblocklife.admin
permission-message: "§c你没有权限使用此命令"
pblstats:
description: 查看插件统计信息
usage: /<command>
permission: playerblocklife.admin
permission-message: "§c你没有权限使用此命令"
permissions:
playerblocklife.*:
description: 所有 PlayerBlockLife 权限
children:
playerblocklife.set: true
playerblocklife.check: true
playerblocklife.admin: true
default: op
playerblocklife.set:
description: 允许设置生命方块
default: true
playerblocklife.check:
description: 允许查看生命方块
default: true
playerblocklife.admin:
description: 管理员权限
default: op