feat: 完善管理员玩家信息查询
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## 未发布
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- 扩展管理员玩家信息查询,增加身份、权限状态、登录历史、实时状态、客户端与网络信息。
|
||||||
|
|
||||||
## 1.3.1 - 2026-08-16
|
## 1.3.1 - 2026-08-16
|
||||||
|
|
||||||
### 移除
|
### 移除
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
- `/hat`
|
- `/hat`
|
||||||
- `/suicide` `(/die)`
|
- `/suicide` `(/die)`
|
||||||
- `/vanish` `(/v)`
|
- `/vanish` `(/v)`
|
||||||
- `/seen` `(/info)`
|
- `/seen` `(/info)` 管理员玩家详细信息查询
|
||||||
- `/tpsbar`
|
- `/tpsbar`
|
||||||
- `/essc admin` 管理模式切换
|
- `/essc admin` 管理模式切换
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ package cn.infstar.essentialsC.commands;
|
|||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
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.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class SeenCommand extends BaseCommand {
|
public class SeenCommand extends BaseCommand {
|
||||||
@@ -49,13 +53,24 @@ public class SeenCommand extends BaseCommand {
|
|||||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
sender.sendMessage(getLang().getPrefixedComponent("messages.seen-header",
|
sender.sendMessage(getLang().getPrefixedComponent("messages.seen-header",
|
||||||
Map.of("player", String.valueOf(target.getName()))));
|
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()) {
|
if (target.isOnline()) {
|
||||||
sender.sendMessage(getLang().getComponent("messages.seen-status-online"));
|
sender.sendMessage(getLang().getComponent("messages.seen-status-online"));
|
||||||
Player onlinePlayer = target.getPlayer();
|
Player onlinePlayer = target.getPlayer();
|
||||||
if (onlinePlayer != null) {
|
if (onlinePlayer != null) {
|
||||||
sender.sendMessage(getLang().getComponent("messages.seen-world",
|
sendOnlineDetails(sender, onlinePlayer);
|
||||||
Map.of("world", onlinePlayer.getWorld().getName())));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(getLang().getComponent("messages.seen-status-offline"));
|
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())))));
|
Map.of("time", format.format(new Date(target.getFirstPlayed())))));
|
||||||
return true;
|
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};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,34 @@ messages:
|
|||||||
seen-usage: '<#FF3300>Error:</#FF3300> <#FF7E5E>Usage: /seen [player]</#FF7E5E>'
|
seen-usage: '<#FF3300>Error:</#FF3300> <#FF7E5E>Usage: /seen [player]</#FF7E5E>'
|
||||||
seen-usage-console: '<#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> <white>{player}</white>'
|
seen-header: '<#00FB9A>Player information:</#00FB9A> <white>{player}</white>'
|
||||||
|
seen-uuid: '<gray>UUID: <white>{uuid}</white></gray>'
|
||||||
|
seen-operator: '<gray>Operator: <white>{value}</white></gray>'
|
||||||
|
seen-whitelisted: '<gray>Whitelisted: <white>{value}</white></gray>'
|
||||||
|
seen-banned: '<gray>Banned: <white>{value}</white></gray>'
|
||||||
|
seen-yes: 'Yes'
|
||||||
|
seen-no: 'No'
|
||||||
|
seen-unknown: 'Unknown'
|
||||||
seen-status-online: '<gray>Status: <#00FB9A>Online</#00FB9A></gray>'
|
seen-status-online: '<gray>Status: <#00FB9A>Online</#00FB9A></gray>'
|
||||||
seen-status-offline: '<gray>Status: <#FF7E5E>Offline</#FF7E5E></gray>'
|
seen-status-offline: '<gray>Status: <#FF7E5E>Offline</#FF7E5E></gray>'
|
||||||
seen-world: '<gray>World: <white>{world}</white></gray>'
|
seen-world: '<gray>World: <white>{world}</white></gray>'
|
||||||
|
seen-location: '<gray>Location: <white>{x}, {y}, {z}</white></gray>'
|
||||||
|
seen-gamemode: '<gray>Game mode: <white>{mode}</white></gray>'
|
||||||
|
seen-gamemode-values:
|
||||||
|
survival: 'Survival'
|
||||||
|
creative: 'Creative'
|
||||||
|
adventure: 'Adventure'
|
||||||
|
spectator: 'Spectator'
|
||||||
|
seen-health: '<gray>Health: <white>{health}/{max_health}</white></gray>'
|
||||||
|
seen-food: '<gray>Food: <white>{food}/20</white></gray>'
|
||||||
|
seen-level: '<gray>Experience level: <white>{level}</white> <gray>Progress: <white>{experience}%</white></gray>'
|
||||||
|
seen-ping: '<gray>Ping: <white>{ping} ms</white></gray>'
|
||||||
|
seen-locale: '<gray>Client locale: <white>{locale}</white></gray>'
|
||||||
|
seen-client: '<gray>Client: <white>{client}</white></gray>'
|
||||||
|
seen-ip: '<gray>IP address: <white>{address}</white></gray>'
|
||||||
|
seen-flying: '<gray>Flying: <white>{value}</white></gray>'
|
||||||
|
seen-playtime: '<gray>Total playtime: <white>{time}</white></gray>'
|
||||||
|
seen-duration: '{days}d {hours}h {minutes}m'
|
||||||
|
seen-last-login: '<gray>Last login: <white>{time}</white></gray>'
|
||||||
seen-last-online: '<gray>Last online: <white>{time}</white></gray>'
|
seen-last-online: '<gray>Last online: <white>{time}</white></gray>'
|
||||||
seen-first-joined: '<gray>First joined: <white>{time}</white></gray>'
|
seen-first-joined: '<gray>First joined: <white>{time}</white></gray>'
|
||||||
player-not-found: '<#FF3300>Error:</#FF3300> <#FF7E5E>Could not find the player {player}.</#FF7E5E>'
|
player-not-found: '<#FF3300>Error:</#FF3300> <#FF7E5E>Could not find the player {player}.</#FF7E5E>'
|
||||||
@@ -95,7 +120,7 @@ help:
|
|||||||
glow: '<white>/glow</white> <gray>- Toggle glowing</gray>'
|
glow: '<white>/glow</white> <gray>- Toggle glowing</gray>'
|
||||||
heal: '<white>/heal [player]</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>'
|
vanish: '<white>/vanish</white> <gray>- Toggle vanish mode</gray>'
|
||||||
seen: '<white>/seen [player]</white> <gray>- View player information</gray>'
|
seen: '<white>/seen [player]</white> <gray>- View detailed player information</gray>'
|
||||||
feed: '<white>/feed [player]</white> <gray>- Restore hunger</gray>'
|
feed: '<white>/feed [player]</white> <gray>- Restore hunger</gray>'
|
||||||
repair: '<white>/repair</white> <gray>- Repair held or all items</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>'
|
tpa: '<white>/tpa [player]</white> <gray>- Request to teleport to a player</gray>'
|
||||||
|
|||||||
@@ -37,9 +37,34 @@ messages:
|
|||||||
seen-usage: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /seen [玩家]</#FF7E5E>'
|
seen-usage: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /seen [玩家]</#FF7E5E>'
|
||||||
seen-usage-console: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /seen [玩家]</#FF7E5E>'
|
seen-usage-console: '<#FF3300>错误:</#FF3300> <#FF7E5E>用法: /seen [玩家]</#FF7E5E>'
|
||||||
seen-header: '<#00FB9A>玩家信息:</#00FB9A> <white>{player}</white>'
|
seen-header: '<#00FB9A>玩家信息:</#00FB9A> <white>{player}</white>'
|
||||||
|
seen-uuid: '<gray>UUID: <white>{uuid}</white></gray>'
|
||||||
|
seen-operator: '<gray>管理员: <white>{value}</white></gray>'
|
||||||
|
seen-whitelisted: '<gray>白名单: <white>{value}</white></gray>'
|
||||||
|
seen-banned: '<gray>封禁状态: <white>{value}</white></gray>'
|
||||||
|
seen-yes: '是'
|
||||||
|
seen-no: '否'
|
||||||
|
seen-unknown: '未知'
|
||||||
seen-status-online: '<gray>状态: <#00FB9A>在线</#00FB9A></gray>'
|
seen-status-online: '<gray>状态: <#00FB9A>在线</#00FB9A></gray>'
|
||||||
seen-status-offline: '<gray>状态: <#FF7E5E>离线</#FF7E5E></gray>'
|
seen-status-offline: '<gray>状态: <#FF7E5E>离线</#FF7E5E></gray>'
|
||||||
seen-world: '<gray>所在世界: <white>{world}</white></gray>'
|
seen-world: '<gray>所在世界: <white>{world}</white></gray>'
|
||||||
|
seen-location: '<gray>坐标: <white>{x}, {y}, {z}</white></gray>'
|
||||||
|
seen-gamemode: '<gray>游戏模式: <white>{mode}</white></gray>'
|
||||||
|
seen-gamemode-values:
|
||||||
|
survival: '生存'
|
||||||
|
creative: '创造'
|
||||||
|
adventure: '冒险'
|
||||||
|
spectator: '旁观'
|
||||||
|
seen-health: '<gray>生命值: <white>{health}/{max_health}</white></gray>'
|
||||||
|
seen-food: '<gray>饥饿值: <white>{food}/20</white></gray>'
|
||||||
|
seen-level: '<gray>经验等级: <white>{level}</white> <gray>进度: <white>{experience}%</white></gray>'
|
||||||
|
seen-ping: '<gray>延迟: <white>{ping} ms</white></gray>'
|
||||||
|
seen-locale: '<gray>客户端语言: <white>{locale}</white></gray>'
|
||||||
|
seen-client: '<gray>客户端: <white>{client}</white></gray>'
|
||||||
|
seen-ip: '<gray>IP 地址: <white>{address}</white></gray>'
|
||||||
|
seen-flying: '<gray>正在飞行: <white>{value}</white></gray>'
|
||||||
|
seen-playtime: '<gray>累计游戏时间: <white>{time}</white></gray>'
|
||||||
|
seen-duration: '{days}天 {hours}小时 {minutes}分钟'
|
||||||
|
seen-last-login: '<gray>最近登录: <white>{time}</white></gray>'
|
||||||
seen-last-online: '<gray>最后在线: <white>{time}</white></gray>'
|
seen-last-online: '<gray>最后在线: <white>{time}</white></gray>'
|
||||||
seen-first-joined: '<gray>首次加入: <white>{time}</white></gray>'
|
seen-first-joined: '<gray>首次加入: <white>{time}</white></gray>'
|
||||||
player-not-found: '<#FF3300>错误:</#FF3300> <#FF7E5E>没有找到玩家 {player}.</#FF7E5E>'
|
player-not-found: '<#FF3300>错误:</#FF3300> <#FF7E5E>没有找到玩家 {player}.</#FF7E5E>'
|
||||||
@@ -95,7 +120,7 @@ help:
|
|||||||
glow: '<white>/glow</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>'
|
vanish: '<white>/vanish</white> <gray>- 切换隐身模式</gray>'
|
||||||
seen: '<white>/seen [玩家]</white> <gray>- 查看玩家信息</gray>'
|
seen: '<white>/seen [玩家]</white> <gray>- 查看玩家详细信息</gray>'
|
||||||
feed: '<white>/feed [玩家]</white> <gray>- 恢复饥饿值</gray>'
|
feed: '<white>/feed [玩家]</white> <gray>- 恢复饥饿值</gray>'
|
||||||
repair: '<white>/repair</white> <gray>- 修复手中或全部物品</gray>'
|
repair: '<white>/repair</white> <gray>- 修复手中或全部物品</gray>'
|
||||||
tpa: '<white>/tpa [玩家]</white> <gray>- 请求传送到玩家身边</gray>'
|
tpa: '<white>/tpa [玩家]</white> <gray>- 请求传送到玩家身边</gray>'
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ permissions:
|
|||||||
description: 允许看见隐身玩家
|
description: 允许看见隐身玩家
|
||||||
default: op
|
default: op
|
||||||
essentialsc.command.seen:
|
essentialsc.command.seen:
|
||||||
description: 允许使用 /seen
|
description: 允许查看玩家详细信息
|
||||||
default: op
|
default: op
|
||||||
essentialsc.command.feed:
|
essentialsc.command.feed:
|
||||||
description: 允许使用 /feed
|
description: 允许使用 /feed
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user