9 Commits
Author SHA1 Message Date
Coldsmile 292b93e842 fix: 修复 1.3.0 实机测试问题
Build / build (push) Has been cancelled
Build / Paper 1.21.11 / Java 21 (push) Has been cancelled
Build / Paper 26.1.2 / Java 25 (push) Has been cancelled
Build / Paper 26.2 / Java 25 (push) Has been cancelled
2026-08-05 00:42:24 +08:00
Coldsmile 7816b75325 feat: 准备 1.3.0 稳定版本 2026-08-02 12:18:04 +08:00
Coldsmile 92ef7f6030 feat: 完善稳定性并重构潜影盒安全机制 2026-08-02 03:36:19 +08:00
Coldsmile d9b1312e0c feat: 完善插件稳定性与兼容性 2026-08-01 00:36:17 +08:00
Coldsmile f8f731f927 feat: 完善 TPA 并整理插件配置 2026-07-31 19:07:50 +08:00
Coldsmile 66bf93b10d merge: 合并 SkinBridge 皮肤兼容功能 2026-07-21 23:45:55 +08:00
Coldsmile d6c8db6019 feat: 添加 SkinBridge 皮肤兼容 2026-07-21 23:45:19 +08:00
Coldsmile 5d86f0e3eb feat: 完整实现 TPA 并添加多版本适配 2026-06-25 23:40:30 +08:00
Coldsmile 061c3be2b1 feat: 完善维护模式与运行期功能管理 2026-06-12 00:08:40 +08:00
19 changed files with 284 additions and 48 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# 更新日志
## 1.4.0 - 2026-08-02
## 1.3.0 - 2026-08-02
### 新增
+9
View File
@@ -45,6 +45,15 @@
- `/essc admin` 管理模式切换
- `/maintenance` `(/maint)` 维护模式管理
### 控制台命令
- `/essc help``/essc version``/essc reload`
- `/maintenance [on|off|status|reload|add|remove|list]`,也可通过 `/essc maintenance ...` 执行
- `/seen [玩家]``/heal [玩家]``/feed [玩家]``/tpsbar [玩家]`
- `/essc skin [status|refresh] [玩家]`
依赖发送者位置、背包或玩家交互状态的命令仅允许在游戏内执行。控制台执行玩家状态命令时必须明确提供目标玩家。
### 其它功能
- Shift + 右键快捷打开潜影盒
+1 -1
View File
@@ -10,7 +10,7 @@ plugins {
}
group = 'cn.infstar'
version = providers.gradleProperty('pluginVersion').orElse('1.4.0').get()
version = providers.gradleProperty('pluginVersion').orElse('1.3.0').get()
repositories {
mavenCentral()
+1 -1
View File
@@ -31,7 +31,7 @@ printf 'online-mode=false\nserver-port=0\nenable-query=false\n' > "$workspace/se
server_pid=$!
for _ in $(seq 1 120); do
if grep -Fq 'EssentialsC v1.4.0 已启用' "$workspace/server.log"; then
if grep -Fq 'EssentialsC v1.3.0 已启用' "$workspace/server.log"; then
if grep -Eq 'UnsupportedClassVersionError|Error occurred while enabling EssentialsC|Could not load.*EssentialsC' "$workspace/server.log"; then
cat "$workspace/server.log"
exit 1
@@ -448,7 +448,8 @@ public final class EssentialsC extends JavaPlugin {
@Override
public Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) {
if (executor instanceof TabCompleter completer) {
return completer.onTabComplete(commandSourceStack.getSender(), commandAdapter, name, args);
String[] completionArgs = args.length == 0 ? new String[]{""} : args;
return completer.onTabComplete(commandSourceStack.getSender(), commandAdapter, name, completionArgs);
}
return List.of();
}
@@ -21,6 +21,9 @@ import java.util.regex.Pattern;
public class LangManager {
private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("(?i)&#([0-9a-f]{6})");
private static final Pattern MINI_MESSAGE_TAG_PATTERN = Pattern.compile(
"(?<!\\\\)(</?)([A-Za-z][A-Za-z0-9_-]*)(?=[:>])"
);
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY_AMPERSAND = LegacyComponentSerializer.legacyAmpersand();
private static final LegacyComponentSerializer LEGACY_SECTION = LegacyComponentSerializer.legacySection();
@@ -161,18 +164,25 @@ public class LangManager {
langFile.setDefaults(selectedDefaults);
}
private String applyPlaceholders(String value, Map<String, String> placeholders) {
static String applyPlaceholders(String value, Map<String, String> placeholders) {
String result = value;
boolean miniMessage = looksLikeMiniMessage(value);
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", escapeMiniMessageReplacement(entry.getValue()));
String replacement = miniMessage
? escapeMiniMessageReplacement(entry.getValue())
: entry.getValue();
result = result.replace("{" + entry.getKey() + "}", replacement == null ? "" : replacement);
}
return result;
}
private String escapeMiniMessageReplacement(String value) {
static String escapeMiniMessageReplacement(String value) {
if (value == null) {
return "";
}
if (value.indexOf('§') >= 0) {
return MINI_MESSAGE.serialize(LEGACY_SECTION.deserialize(value));
}
return value.replace("\\", "\\\\").replace("<", "\\<");
}
@@ -213,7 +223,7 @@ public class LangManager {
}
if (looksLikeMiniMessage(text)) {
try {
return MINI_MESSAGE.deserialize(text);
return MINI_MESSAGE.deserialize(normalizeMiniMessageTags(text));
} catch (RuntimeException ignored) {
// 配置中 MiniMessage 语法错误时回退到旧颜色码解析,避免消息完全不可用。
}
@@ -221,8 +231,20 @@ public class LangManager {
return LEGACY_SECTION.deserialize(translateColorCodes(text));
}
private boolean looksLikeMiniMessage(String text) {
private static boolean looksLikeMiniMessage(String text) {
int open = text.indexOf('<');
return open >= 0 && text.indexOf('>', open) > open;
}
static String normalizeMiniMessageTags(String text) {
Matcher matcher = MINI_MESSAGE_TAG_PATTERN.matcher(text);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
matcher.appendReplacement(result, Matcher.quoteReplacement(
matcher.group(1) + matcher.group(2).toLowerCase(java.util.Locale.ROOT)
));
}
matcher.appendTail(result);
return result.toString();
}
}
@@ -49,12 +49,17 @@ public abstract class BaseCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
return dispatch(sender, args);
}
final boolean dispatch(CommandSender sender, String[] args) {
if (!sender.hasPermission(permission)) {
sender.sendMessage(getLang().getPrefixedString("messages.no-permission",
Map.of("permission", permission)));
return true;
}
if (sender instanceof Player player) {
if (!player.hasPermission(permission)) {
player.sendMessage(getLang().getPrefixedString("messages.no-permission",
Map.of("permission", permission)));
return true;
}
return execute(player, args);
}
@@ -1,6 +1,7 @@
package cn.infstar.essentialsC.commands;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Map;
@@ -36,6 +37,30 @@ public class FeedCommand extends BaseCommand {
return true;
}
@Override
protected boolean executeConsole(CommandSender sender, String[] args) {
if (args.length != 1) {
sender.sendMessage(getLang().getPrefixedString("messages.feed-usage-console"));
return true;
}
if (!sender.hasPermission("essentialsc.command.feed.others")) {
sender.sendMessage(getLang().getPrefixedString("messages.no-permission-others"));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
return true;
}
feedPlayer(target);
sender.sendMessage(getLang().getPrefixedString("messages.feed-other", Map.of("player", target.getName())));
target.sendMessage(getLang().getPrefixedString("messages.feed-by-other",
Map.of("admin", getLang().getString("messages.console-name"))));
return true;
}
private void feedPlayer(Player player) {
player.setFoodLevel(20);
player.setSaturation(20f);
@@ -3,6 +3,7 @@ package cn.infstar.essentialsC.commands;
import org.bukkit.Bukkit;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Map;
@@ -38,6 +39,30 @@ public class HealCommand extends BaseCommand {
return true;
}
@Override
protected boolean executeConsole(CommandSender sender, String[] args) {
if (args.length != 1) {
sender.sendMessage(getLang().getPrefixedString("messages.heal-usage-console"));
return true;
}
if (!sender.hasPermission("essentialsc.command.heal.others")) {
sender.sendMessage(getLang().getPrefixedString("messages.no-permission-others"));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
return true;
}
healPlayer(target);
sender.sendMessage(getLang().getPrefixedString("messages.heal-other", Map.of("player", target.getName())));
target.sendMessage(getLang().getPrefixedString("messages.heal-by-other",
Map.of("admin", getLang().getString("messages.console-name"))));
return true;
}
private void healPlayer(Player player) {
AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);
player.setHealth(maxHealth == null ? player.getHealth() : maxHealth.getValue());
@@ -38,7 +38,12 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
@Override
protected boolean executeConsole(CommandSender sender, String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
sendConsoleHelp(sender);
return true;
}
if (args[0].equalsIgnoreCase("reload")) {
if (!sender.hasPermission("essentialsc.command.reload")) {
sendNoPermission(sender, "essentialsc.command.reload");
return true;
@@ -51,10 +56,62 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
sender.sendMessage(getLang().getPrefixedString("messages.config-reloaded"));
return true;
}
sender.sendMessage(getLang().getPrefixedString("messages.player-only"));
if (args[0].equalsIgnoreCase("version") || args[0].equalsIgnoreCase("v")) {
sender.sendMessage(getLang().getPrefixedString("messages.version",
Map.of("version", plugin.getPluginMeta().getVersion())));
sender.sendMessage(getLang().getPrefixedString("messages.paper-version",
Map.of("version", Bukkit.getVersion())));
return true;
}
String actualCommand = getActualCommand(args[0]);
BaseCommand targetCommand = CommandRegistry.getCommand(actualCommand);
if (actualCommand != null && targetCommand != null) {
return targetCommand.dispatch(sender, Arrays.copyOfRange(args, 1, args.length));
}
sender.sendMessage(getLang().getPrefixedString("messages.unknown-subcommand",
Map.of("command", args[0])));
sender.sendMessage(getLang().getPrefixedString("messages.help-usage"));
return true;
}
private void sendConsoleHelp(CommandSender sender) {
LangManager lang = getLang();
sendPrefixed(sender, lang.getString("help.title"));
sendPrefixed(sender, lang.getString("help.version",
Map.of("version", plugin.getPluginMeta().getVersion())));
sender.sendMessage("");
sendPrefixed(sender, lang.getString("help.section-other"));
if (sender.hasPermission("essentialsc.command.reload")) {
sendPrefixed(sender, lang.getString("help.commands.reload"));
}
sendPrefixed(sender, lang.getString("help.commands.version"));
if (CommandRegistry.isAvailable("seen") && sender.hasPermission("essentialsc.command.seen")) {
sendPrefixed(sender, lang.getString("help.commands.seen"));
}
if (CommandRegistry.isAvailable("heal") && sender.hasPermission("essentialsc.command.heal")) {
sendPrefixed(sender, lang.getString("help.commands.heal"));
}
if (CommandRegistry.isAvailable("feed") && sender.hasPermission("essentialsc.command.feed")) {
sendPrefixed(sender, lang.getString("help.commands.feed"));
}
if (CommandRegistry.isAvailable("skin") && sender.hasPermission("essentialsc.command.skin")) {
sendPrefixed(sender, lang.getString("help.commands.skin"));
}
if (CommandRegistry.isAvailable("tpsbar") && sender.hasPermission("essentialsc.command.tpsbar")) {
sendPrefixed(sender, lang.getString("help.commands.tpsbar"));
}
if (CommandRegistry.isAvailable("maintenance")
&& sender.hasPermission("essentialsc.command.maintenance")) {
sendPrefixed(sender, lang.getString("help.commands.maintenance"));
}
sender.sendMessage("");
sendPrefixed(sender, lang.getString("help.footer"));
}
private boolean handleCommand(CommandSender sender, Player player, String[] args) {
if (args.length > 0) {
String subCommand = args[0].toLowerCase();
@@ -246,7 +303,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
}
private void sendPrefixed(CommandSender sender, String message) {
sender.sendMessage(message);
sender.sendMessage(getLang().getPrefix() + message);
}
private void sendPrefixedLines(CommandSender sender, String message) {
@@ -2,6 +2,7 @@ package cn.infstar.essentialsC.commands;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
@@ -16,45 +17,56 @@ public class SeenCommand extends BaseCommand {
@Override
protected boolean execute(Player player, String[] args) {
return executeCommand(player, player, args);
}
@Override
protected boolean executeConsole(CommandSender sender, String[] args) {
return executeCommand(sender, null, args);
}
private boolean executeCommand(CommandSender sender, Player viewer, String[] args) {
if (args.length == 0) {
player.sendMessage(getLang().getPrefixedString("messages.seen-usage"));
sender.sendMessage(getLang().getPrefixedString(viewer == null
? "messages.seen-usage-console"
: "messages.seen-usage"));
return true;
}
Player onlineTarget = Bukkit.getPlayerExact(args[0]);
if (onlineTarget != null && VanishCommand.isVanished(onlineTarget)
&& !player.hasPermission(VanishCommand.SEE_PERMISSION)) {
player.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
&& viewer != null && !viewer.hasPermission(VanishCommand.SEE_PERMISSION)) {
sender.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
return true;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[0]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
player.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
sender.sendMessage(getLang().getPrefixedString("messages.player-not-found", Map.of("player", args[0])));
return true;
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
player.sendMessage(getLang().getPrefixedComponent("messages.seen-header",
sender.sendMessage(getLang().getPrefixedComponent("messages.seen-header",
Map.of("player", String.valueOf(target.getName()))));
if (target.isOnline()) {
player.sendMessage(getLang().getComponent("messages.seen-status-online"));
sender.sendMessage(getLang().getComponent("messages.seen-status-online"));
Player onlinePlayer = target.getPlayer();
if (onlinePlayer != null) {
player.sendMessage(getLang().getComponent("messages.seen-world",
sender.sendMessage(getLang().getComponent("messages.seen-world",
Map.of("world", onlinePlayer.getWorld().getName())));
}
} else {
player.sendMessage(getLang().getComponent("messages.seen-status-offline"));
sender.sendMessage(getLang().getComponent("messages.seen-status-offline"));
long lastSeen = target.getLastSeen();
if (lastSeen > 0) {
player.sendMessage(getLang().getComponent("messages.seen-last-online",
sender.sendMessage(getLang().getComponent("messages.seen-last-online",
Map.of("time", format.format(new Date(lastSeen)))));
}
}
player.sendMessage(getLang().getComponent("messages.seen-first-joined",
sender.sendMessage(getLang().getComponent("messages.seen-first-joined",
Map.of("time", format.format(new Date(target.getFirstPlayed())))));
return true;
}
@@ -63,17 +63,37 @@ public class TpsBarCommand extends BaseCommand implements TabCompleter {
@Override
protected boolean executeConsole(CommandSender sender, String[] args) {
sender.sendMessage(getLang().getPrefixedString("messages.player-only"));
TpsBarService tpsBarService = plugin.getTpsBarManager();
if (tpsBarService == null) {
sender.sendMessage(getLang().getPrefixedString("messages.module-disabled"));
return true;
}
if (args.length != 1) {
sender.sendMessage(tpsBarService.getUsageMessage());
return true;
}
if (!sender.hasPermission("essentialsc.command.tpsbar.others")) {
sender.sendMessage(getLang().getPrefixedString("messages.no-permission",
Map.of("permission", "essentialsc.command.tpsbar.others")));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage(tpsBarService.getPlayerNotFoundMessage(args[0]));
return true;
}
boolean enabled = tpsBarService.toggle(target);
sender.sendMessage(getLang().getPrefixedString(enabled
? "tpsbar.messages.enabled-other"
: "tpsbar.messages.disabled-other", Map.of("player", target.getName())));
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length != 1 || !(sender instanceof Player player)) {
return List.of();
}
if (!player.hasPermission("essentialsc.command.tpsbar.others")) {
if (args.length != 1 || !sender.hasPermission("essentialsc.command.tpsbar.others")) {
return List.of();
}
@@ -109,7 +109,7 @@ public class ShulkerBoxListener implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
@@ -120,6 +120,10 @@ public class ShulkerBoxListener implements Listener {
return;
}
if (event.useItemInHand() == org.bukkit.event.Event.Result.DENY) {
return;
}
if (openShulkerBoxes.containsKey(player.getUniqueId())) {
return;
}
@@ -45,7 +45,9 @@ public final class MaintenanceListener implements Listener {
}
MaintenanceManager.AccessSnapshot snapshot = maintenanceManager.getAccessSnapshot();
if (!snapshot.enabled() || snapshot.isWhitelisted(event.getUniqueId(), event.getName())) {
if (!snapshot.enabled()
|| snapshot.isWhitelisted(event.getUniqueId(), event.getName())
|| maintenanceManager.isOperator(event.getUniqueId())) {
return;
}
@@ -12,6 +12,7 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import java.io.File;
import java.io.IOException;
@@ -30,13 +31,18 @@ public final class MaintenanceManager {
private final EssentialsC plugin;
private final File configFile;
private final BukkitTask operatorCacheTask;
private FileConfiguration config;
private BossBar bossBar;
private volatile AccessSnapshot accessSnapshot = AccessSnapshot.disabled();
private volatile Set<UUID> operatorUuids = Set.of();
public MaintenanceManager(EssentialsC plugin) {
this.plugin = plugin;
this.configFile = new File(plugin.getDataFolder(), "maintenance.yml");
refreshOperatorCache();
this.operatorCacheTask = plugin.getServer().getScheduler()
.runTaskTimer(plugin, this::refreshOperatorCache, 20L, 20L);
reload();
}
@@ -109,7 +115,11 @@ public final class MaintenanceManager {
}
public boolean canJoin(Player player) {
return player != null && (player.hasPermission(getBypassPermission()) || isWhitelisted(player));
return player != null && (player.isOp() || player.hasPermission(getBypassPermission()) || isWhitelisted(player));
}
public boolean isOperator(UUID uniqueId) {
return uniqueId != null && operatorUuids.contains(uniqueId);
}
public AccessSnapshot getAccessSnapshot() {
@@ -231,10 +241,20 @@ public final class MaintenanceManager {
}
public void shutdown() {
operatorCacheTask.cancel();
accessSnapshot = AccessSnapshot.disabled();
operatorUuids = Set.of();
clearBossBar();
}
private void refreshOperatorCache() {
Set<UUID> operators = new LinkedHashSet<>();
for (org.bukkit.OfflinePlayer operator : plugin.getServer().getOperators()) {
operators.add(operator.getUniqueId());
}
operatorUuids = Set.copyOf(operators);
}
private void clearBossBar() {
if (bossBar == null) {
return;
+2 -2
View File
@@ -29,11 +29,11 @@ mob-drops:
# 允许末影人掉落搬运的方块。
allow-drops: true
# TPA 传送请求,行为参考 HuskHomes。
# TPA 传送请求
tpa:
# 请求有效时间,单位为秒。
timeout-seconds: 60
# 每名接收者最多保留的待处理请求数;参考 EssentialsX超出时移除最早请求。
# 每名接收者最多保留的待处理请求数;超出时移除最早请求。
max-pending-requests: 5
# 位置规则
+10 -5
View File
@@ -1,11 +1,12 @@
# English language file
# HuskHomes-inspired style: MiniMessage, green success, red errors, gray details.
# MiniMessage format with green success, red errors, and gray details.
prefix: ''
prefix: '<#00FB9A><bold>[EssentialsC]</bold></#00FB9A><gray>: </gray>'
messages:
no-permission: '<#FF3300>Error:</#FF3300> <#FF7E5E>You do not have permission to use this command.</#FF7E5E> <gray>Required: {permission}</gray>'
player-only: '<#FF3300>Error:</#FF3300> <#FF7E5E>This command can only be run by players.</#FF7E5E>'
console-name: 'Console'
config-reloaded: '<#00FB9A>Configuration reloaded.</#00FB9A>'
version: '<#00FB9A>EssentialsC v{version}</#00FB9A>'
paper-version: '<gray>Running on Paper {version}</gray>'
@@ -49,9 +50,11 @@ messages:
heal-self: '<#00FB9A>Your health and hunger have been restored.</#00FB9A>'
heal-other: '<#00FB9A>You healed <bold>{player}</bold>.</#00FB9A>'
heal-by-other: '<#00FB9A>You were healed by <bold>{admin}</bold>.</#00FB9A>'
heal-usage-console: '<#FF3300>Error:</#FF3300> <#FF7E5E>Usage: /heal [player]</#FF7E5E>'
feed-self: '<#00FB9A>Your hunger has been restored.</#00FB9A>'
feed-other: '<#00FB9A>You fed <bold>{player}</bold>.</#00FB9A>'
feed-by-other: '<#00FB9A>You were fed by <bold>{admin}</bold>.</#00FB9A>'
feed-usage-console: '<#FF3300>Error:</#FF3300> <#FF7E5E>Usage: /feed [player]</#FF7E5E>'
repair-hand-success: '<#00FB9A>Repaired the item in your hand.</#00FB9A>'
repair-all-success: '<#00FB9A>Repaired <bold>{count}</bold> item(s).</#00FB9A>'
repair-not-damaged: '<#FF3300>Error:</#FF3300> <#FF7E5E>This item is not damaged.</#FF7E5E>'
@@ -76,6 +79,8 @@ help:
footer: '<gray>Each command requires the corresponding permission.</gray>'
commands:
reload: '<white>/essc reload</white> <gray>- Reload plugin configuration</gray>'
version: '<white>/essc version</white> <gray>- View plugin and server versions</gray>'
workbench: '<white>/workbench</white> <gray>- Open a crafting table</gray>'
anvil: '<white>/anvil</white> <gray>- Open an anvil</gray>'
enchantingtable: '<white>/enchantingtable</white> <gray>- Open an enchanting table</gray>'
@@ -91,10 +96,10 @@ help:
fly: '<white>/fly</white> <gray>- Toggle flight mode</gray>'
nightvision: '<white>/nightvision</white> <gray>- Toggle night vision</gray>'
glow: '<white>/glow</white> <gray>- Toggle glowing</gray>'
heal: '<white>/heal</white> <gray>- Restore health and hunger</gray>'
heal: '<white>/heal [player]</white> <gray>- Restore health and hunger</gray>'
vanish: '<white>/vanish</white> <gray>- Toggle vanish mode</gray>'
seen: '<white>/seen</white> <gray>- View player information</gray>'
feed: '<white>/feed</white> <gray>- Restore hunger</gray>'
seen: '<white>/seen [player]</white> <gray>- View player information</gray>'
feed: '<white>/feed [player]</white> <gray>- Restore hunger</gray>'
repair: '<white>/repair</white> <gray>- Repair held or all items</gray>'
tpa: '<white>/tpa [player]</white> <gray>- Request to teleport to a player</gray>'
tpahere: '<white>/tpahere [player]</white> <gray>- Request a player teleport to you</gray>'
+10 -5
View File
@@ -1,11 +1,12 @@
# 简体中文语言文件
# 文本风格参考 HuskHomesMiniMessage绿色成功、红色错误灰色说明。
# 支持 MiniMessage 格式,默认使用绿色成功、红色错误灰色说明。
prefix: ''
prefix: '<#00FB9A><bold>[EssentialsC]</bold></#00FB9A><gray>: </gray>'
messages:
no-permission: '<#FF3300>错误:</#FF3300> <#FF7E5E>你没有权限执行此命令.</#FF7E5E> <gray>需要权限: {permission}</gray>'
player-only: '<#FF3300>错误:</#FF3300> <#FF7E5E>此命令只能由玩家执行.</#FF7E5E>'
console-name: '控制台'
config-reloaded: '<#00FB9A>配置已重载.</#00FB9A>'
version: '<#00FB9A>EssentialsC v{version}</#00FB9A>'
paper-version: '<gray>当前运行于 Paper {version}</gray>'
@@ -49,9 +50,11 @@ messages:
heal-self: '<#00FB9A>你的生命值和饥饿值已恢复.</#00FB9A>'
heal-other: '<#00FB9A>你已治疗玩家 <bold>{player}</bold>.</#00FB9A>'
heal-by-other: '<#00FB9A>管理员 <bold>{admin}</bold> 治疗了你.</#00FB9A>'
heal-usage-console: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /heal [玩家]</#FF7E5E>'
feed-self: '<#00FB9A>你的饥饿值已恢复.</#00FB9A>'
feed-other: '<#00FB9A>你已喂饱玩家 <bold>{player}</bold>.</#00FB9A>'
feed-by-other: '<#00FB9A>管理员 <bold>{admin}</bold> 喂饱了你.</#00FB9A>'
feed-usage-console: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /feed [玩家]</#FF7E5E>'
repair-hand-success: '<#00FB9A>手中物品已修复.</#00FB9A>'
repair-all-success: '<#00FB9A>已修复 <bold>{count}</bold> 个物品.</#00FB9A>'
repair-not-damaged: '<#FF3300>错误:</#FF3300> <#FF7E5E>该物品没有损坏.</#FF7E5E>'
@@ -76,6 +79,8 @@ help:
footer: '<gray>每个命令都需要对应权限.</gray>'
commands:
reload: '<white>/essc reload</white> <gray>- 重载插件配置</gray>'
version: '<white>/essc version</white> <gray>- 查看插件与服务端版本</gray>'
workbench: '<white>/workbench</white> <gray>- 打开工作台</gray>'
anvil: '<white>/anvil</white> <gray>- 打开铁砧</gray>'
enchantingtable: '<white>/enchantingtable</white> <gray>- 打开附魔台</gray>'
@@ -91,10 +96,10 @@ help:
fly: '<white>/fly</white> <gray>- 切换飞行模式</gray>'
nightvision: '<white>/nightvision</white> <gray>- 切换夜视</gray>'
glow: '<white>/glow</white> <gray>- 切换发光</gray>'
heal: '<white>/heal</white> <gray>- 恢复生命值和饥饿值</gray>'
heal: '<white>/heal [玩家]</white> <gray>- 恢复生命值和饥饿值</gray>'
vanish: '<white>/vanish</white> <gray>- 切换隐身模式</gray>'
seen: '<white>/seen</white> <gray>- 查看玩家信息</gray>'
feed: '<white>/feed</white> <gray>- 恢复饥饿值</gray>'
seen: '<white>/seen [玩家]</white> <gray>- 查看玩家信息</gray>'
feed: '<white>/feed [玩家]</white> <gray>- 恢复饥饿值</gray>'
repair: '<white>/repair</white> <gray>- 修复手中或全部物品</gray>'
tpa: '<white>/tpa [玩家]</white> <gray>- 请求传送到玩家身边</gray>'
tpahere: '<white>/tpahere [玩家]</white> <gray>- 请求玩家传送到你身边</gray>'
@@ -4,6 +4,8 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.api.Test;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -40,6 +42,9 @@ class ConfigurationResourcesTest {
if (resourcePath.equals("paper-plugin.yml")) {
assertEquals("1.21.11", configuration.getString("api-version"));
}
if (resourcePath.startsWith("lang/")) {
assertTrue(!configuration.getString("prefix", "").isBlank());
}
}
}
@@ -51,6 +56,25 @@ class ConfigurationResourcesTest {
assertEquals(chinese.getKeys(true), english.getKeys(true));
}
@Test
void miniMessageTagNamesAreNormalized() {
assertEquals("<gray>文本</gray>", LangManager.normalizeMiniMessageTags("<GRAY>文本</GRAY>"));
assertEquals("\\<GRAY>", LangManager.normalizeMiniMessageTags("\\<GRAY>"));
}
@Test
void legacyFormattingCanBeNestedInMiniMessage() {
String message = LangManager.applyPlaceholders(
"<gray>维护状态: {status}</gray>",
java.util.Map.of("status", "§a开启")
);
String rendered = PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(message)
);
assertEquals("维护状态: 开启", rendered);
}
private YamlConfiguration loadResource(String resourcePath) throws Exception {
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resourcePath)) {
assertNotNull(input, resourcePath);