Compare commits
16 Commits
e40fcb344f
...
v2.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fbf5cfd7d | ||
|
|
8b502459b0 | ||
|
|
f899540449 | ||
|
|
1fbd92ec72 | ||
|
|
c15b30f666 | ||
|
|
8d8cdcb244 | ||
|
|
c7d09c3039 | ||
|
|
a3cb82c1b7 | ||
|
|
c9035d488c | ||
|
|
e3b46b6946 | ||
|
|
f92fdb40c6 | ||
|
|
dd6c570fa7 | ||
|
|
68fadf4e1a | ||
|
|
1e6b0cb19e | ||
|
|
e588ca9866 | ||
| 9e113fc37d |
19
LICENSE
19
LICENSE
@@ -1,7 +1,18 @@
|
||||
Copyright © 2026 FunNetworkXiaobai
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Copyright (c) 2026 xiaobai
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = 'com.playerblocklife'
|
||||
version = '1.0.0'
|
||||
version = '2.0.0-1.20.4'
|
||||
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
|
||||
@@ -5,5 +5,5 @@ org.gradle.caching=true
|
||||
org.gradle.daemon=true
|
||||
|
||||
# ????
|
||||
pluginVersion=1.0.0
|
||||
pluginVersion=2.0.0-1.20.4
|
||||
mcVersion=1.20.4
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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("&", "§");
|
||||
}
|
||||
|
||||
183
src/main/java/com/playerblocklife/MessageManager.java
Normal file
183
src/main/java/com/playerblocklife/MessageManager.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 寻找合适的位置
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# PlayerBlockLife 配置文件
|
||||
# 版本: 1.0.0
|
||||
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"
|
||||
108
src/main/resources/messages.yml
Normal file
108
src/main/resources/messages.yml
Normal 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} - 命令用法
|
||||
@@ -1,35 +1,75 @@
|
||||
name: PlayerBlockLife
|
||||
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
|
||||
Reference in New Issue
Block a user