diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f88539..1343c70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# 更新日志
+## 未发布
+
+### 优化
+
+- 扩展管理员玩家信息查询,增加身份、权限状态、登录历史、实时状态、客户端与网络信息。
+
## 1.3.1 - 2026-08-16
### 移除
diff --git a/README.md b/README.md
index 1199ae0..70153b5 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@
- `/hat`
- `/suicide` `(/die)`
- `/vanish` `(/v)`
-- `/seen` `(/info)`
+- `/seen` `(/info)` 管理员玩家详细信息查询
- `/tpsbar`
- `/essc admin` 管理模式切换
diff --git a/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java b/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java
index cdc3c66..3fbd356 100644
--- a/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java
+++ b/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java
@@ -2,11 +2,15 @@ package cn.infstar.essentialsC.commands;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
+import org.bukkit.Statistic;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
public class SeenCommand extends BaseCommand {
@@ -49,13 +53,24 @@ public class SeenCommand extends BaseCommand {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sender.sendMessage(getLang().getPrefixedComponent("messages.seen-header",
Map.of("player", String.valueOf(target.getName()))));
+ sender.sendMessage(getLang().getComponent("messages.seen-uuid",
+ Map.of("uuid", target.getUniqueId().toString())));
+ sender.sendMessage(getLang().getComponent("messages.seen-operator",
+ Map.of("value", getLang().getString(target.isOp() ? "messages.seen-yes" : "messages.seen-no"))));
+ sender.sendMessage(getLang().getComponent("messages.seen-whitelisted",
+ Map.of("value", getLang().getString(target.isWhitelisted() ? "messages.seen-yes" : "messages.seen-no"))));
+ sender.sendMessage(getLang().getComponent("messages.seen-banned",
+ Map.of("value", getLang().getString(target.isBanned() ? "messages.seen-yes" : "messages.seen-no"))));
+ if (target.getLastLogin() > 0) {
+ sender.sendMessage(getLang().getComponent("messages.seen-last-login",
+ Map.of("time", format.format(new Date(target.getLastLogin())))));
+ }
if (target.isOnline()) {
sender.sendMessage(getLang().getComponent("messages.seen-status-online"));
Player onlinePlayer = target.getPlayer();
if (onlinePlayer != null) {
- sender.sendMessage(getLang().getComponent("messages.seen-world",
- Map.of("world", onlinePlayer.getWorld().getName())));
+ sendOnlineDetails(sender, onlinePlayer);
}
} else {
sender.sendMessage(getLang().getComponent("messages.seen-status-offline"));
@@ -70,4 +85,74 @@ public class SeenCommand extends BaseCommand {
Map.of("time", format.format(new Date(target.getFirstPlayed())))));
return true;
}
+
+ private void sendOnlineDetails(CommandSender sender, Player player) {
+ var location = player.getLocation();
+ String address = player.getAddress() == null || player.getAddress().getAddress() == null
+ ? getLang().getString("messages.seen-unknown")
+ : player.getAddress().getAddress().getHostAddress();
+ String clientBrand = player.getClientBrandName();
+ if (clientBrand == null || clientBrand.isBlank()) {
+ clientBrand = getLang().getString("messages.seen-unknown");
+ }
+ AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.MAX_HEALTH);
+ double maxHealth = maxHealthAttribute == null ? player.getHealth() : maxHealthAttribute.getValue();
+
+ sender.sendMessage(getLang().getComponent("messages.seen-world",
+ Map.of("world", player.getWorld().getName())));
+ sender.sendMessage(getLang().getComponent("messages.seen-location", Map.of(
+ "x", formatNumber(location.getX()),
+ "y", formatNumber(location.getY()),
+ "z", formatNumber(location.getZ())
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-gamemode", Map.of(
+ "mode", getLang().getString("messages.seen-gamemode-values."
+ + player.getGameMode().name().toLowerCase(Locale.ROOT))
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-health", Map.of(
+ "health", formatNumber(player.getHealth()),
+ "max_health", formatNumber(maxHealth)
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-food", Map.of(
+ "food", Integer.toString(player.getFoodLevel())
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-level", Map.of(
+ "level", Integer.toString(player.getLevel()),
+ "experience", formatNumber(player.getExp() * 100.0D)
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-ping", Map.of(
+ "ping", Integer.toString(Math.max(0, player.getPing()))
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-locale", Map.of(
+ "locale", player.locale().toLanguageTag()
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-client", Map.of(
+ "client", clientBrand
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-ip", Map.of("address", address)));
+ sender.sendMessage(getLang().getComponent("messages.seen-flying", Map.of(
+ "value", getLang().getString(player.isFlying() ? "messages.seen-yes" : "messages.seen-no")
+ )));
+ sender.sendMessage(getLang().getComponent("messages.seen-playtime", Map.of(
+ "time", formatDuration(player.getStatistic(Statistic.PLAY_ONE_MINUTE))
+ )));
+ }
+
+ private String formatNumber(double value) {
+ return String.format(Locale.ROOT, "%.1f", value);
+ }
+
+ private String formatDuration(int ticks) {
+ long[] duration = durationParts(ticks);
+ return getLang().getString("messages.seen-duration", Map.of(
+ "days", Long.toString(duration[0]),
+ "hours", Long.toString(duration[1]),
+ "minutes", Long.toString(duration[2])
+ ));
+ }
+
+ static long[] durationParts(int ticks) {
+ long minutes = Math.max(0L, ticks) / (20L * 60L);
+ return new long[]{minutes / (24L * 60L), minutes % (24L * 60L) / 60L, minutes % 60L};
+ }
}
diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml
index 75aaf0e..1e310db 100644
--- a/src/main/resources/lang/en_US.yml
+++ b/src/main/resources/lang/en_US.yml
@@ -37,9 +37,34 @@ messages:
seen-usage: '<#FF3300>Error:#FF3300> <#FF7E5E>Usage: /seen [player]#FF7E5E>'
seen-usage-console: '<#FF3300>Error:#FF3300> <#FF7E5E>Usage: /seen [player]#FF7E5E>'
seen-header: '<#00FB9A>Player information:#00FB9A> {player}'
+ seen-uuid: 'UUID: {uuid}'
+ seen-operator: 'Operator: {value}'
+ seen-whitelisted: 'Whitelisted: {value}'
+ seen-banned: 'Banned: {value}'
+ seen-yes: 'Yes'
+ seen-no: 'No'
+ seen-unknown: 'Unknown'
seen-status-online: 'Status: <#00FB9A>Online#00FB9A>'
seen-status-offline: 'Status: <#FF7E5E>Offline#FF7E5E>'
seen-world: 'World: {world}'
+ seen-location: 'Location: {x}, {y}, {z}'
+ seen-gamemode: 'Game mode: {mode}'
+ seen-gamemode-values:
+ survival: 'Survival'
+ creative: 'Creative'
+ adventure: 'Adventure'
+ spectator: 'Spectator'
+ seen-health: 'Health: {health}/{max_health}'
+ seen-food: 'Food: {food}/20'
+ seen-level: 'Experience level: {level} Progress: {experience}%'
+ seen-ping: 'Ping: {ping} ms'
+ seen-locale: 'Client locale: {locale}'
+ seen-client: 'Client: {client}'
+ seen-ip: 'IP address: {address}'
+ seen-flying: 'Flying: {value}'
+ seen-playtime: 'Total playtime: {time}'
+ seen-duration: '{days}d {hours}h {minutes}m'
+ seen-last-login: 'Last login: {time}'
seen-last-online: 'Last online: {time}'
seen-first-joined: 'First joined: {time}'
player-not-found: '<#FF3300>Error:#FF3300> <#FF7E5E>Could not find the player {player}.#FF7E5E>'
@@ -95,7 +120,7 @@ help:
glow: '/glow - Toggle glowing'
heal: '/heal [player] - Restore health and hunger'
vanish: '/vanish - Toggle vanish mode'
- seen: '/seen [player] - View player information'
+ seen: '/seen [player] - View detailed player information'
feed: '/feed [player] - Restore hunger'
repair: '/repair - Repair held or all items'
tpa: '/tpa [player] - Request to teleport to a player'
diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml
index 0a82d4b..445a677 100644
--- a/src/main/resources/lang/zh_CN.yml
+++ b/src/main/resources/lang/zh_CN.yml
@@ -37,9 +37,34 @@ messages:
seen-usage: '<#FF3300>错误:#FF3300> <#FF7E5E>用法: /seen [玩家]#FF7E5E>'
seen-usage-console: '<#FF3300>错误:#FF3300> <#FF7E5E>用法: /seen [玩家]#FF7E5E>'
seen-header: '<#00FB9A>玩家信息:#00FB9A> {player}'
+ seen-uuid: 'UUID: {uuid}'
+ seen-operator: '管理员: {value}'
+ seen-whitelisted: '白名单: {value}'
+ seen-banned: '封禁状态: {value}'
+ seen-yes: '是'
+ seen-no: '否'
+ seen-unknown: '未知'
seen-status-online: '状态: <#00FB9A>在线#00FB9A>'
seen-status-offline: '状态: <#FF7E5E>离线#FF7E5E>'
seen-world: '所在世界: {world}'
+ seen-location: '坐标: {x}, {y}, {z}'
+ seen-gamemode: '游戏模式: {mode}'
+ seen-gamemode-values:
+ survival: '生存'
+ creative: '创造'
+ adventure: '冒险'
+ spectator: '旁观'
+ seen-health: '生命值: {health}/{max_health}'
+ seen-food: '饥饿值: {food}/20'
+ seen-level: '经验等级: {level} 进度: {experience}%'
+ seen-ping: '延迟: {ping} ms'
+ seen-locale: '客户端语言: {locale}'
+ seen-client: '客户端: {client}'
+ seen-ip: 'IP 地址: {address}'
+ seen-flying: '正在飞行: {value}'
+ seen-playtime: '累计游戏时间: {time}'
+ seen-duration: '{days}天 {hours}小时 {minutes}分钟'
+ seen-last-login: '最近登录: {time}'
seen-last-online: '最后在线: {time}'
seen-first-joined: '首次加入: {time}'
player-not-found: '<#FF3300>错误:#FF3300> <#FF7E5E>没有找到玩家 {player}.#FF7E5E>'
@@ -95,7 +120,7 @@ help:
glow: '/glow - 切换发光'
heal: '/heal [玩家] - 恢复生命值和饥饿值'
vanish: '/vanish - 切换隐身模式'
- seen: '/seen [玩家] - 查看玩家信息'
+ seen: '/seen [玩家] - 查看玩家详细信息'
feed: '/feed [玩家] - 恢复饥饿值'
repair: '/repair - 修复手中或全部物品'
tpa: '/tpa [玩家] - 请求传送到玩家身边'
diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml
index acf08a9..875d120 100644
--- a/src/main/resources/paper-plugin.yml
+++ b/src/main/resources/paper-plugin.yml
@@ -66,7 +66,7 @@ permissions:
description: 允许看见隐身玩家
default: op
essentialsc.command.seen:
- description: 允许使用 /seen
+ description: 允许查看玩家详细信息
default: op
essentialsc.command.feed:
description: 允许使用 /feed
diff --git a/src/test/java/cn/infstar/essentialsC/commands/SeenCommandTest.java b/src/test/java/cn/infstar/essentialsC/commands/SeenCommandTest.java
new file mode 100644
index 0000000..5a9e8cb
--- /dev/null
+++ b/src/test/java/cn/infstar/essentialsC/commands/SeenCommandTest.java
@@ -0,0 +1,16 @@
+package cn.infstar.essentialsC.commands;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+class SeenCommandTest {
+
+ @Test
+ void convertsPlayTicksToDaysHoursAndMinutes() {
+ int ticks = (2 * 24 * 60 + 3 * 60 + 4) * 60 * 20;
+
+ assertArrayEquals(new long[]{2, 3, 4}, SeenCommand.durationParts(ticks));
+ assertArrayEquals(new long[]{0, 0, 0}, SeenCommand.durationParts(-1));
+ }
+}