update
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 25s
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 25s
This commit is contained in:
@@ -18,11 +18,13 @@ import org.xgqy.survival.command.InventoryCommandExecutor;
|
|||||||
import org.xgqy.survival.command.KillCommandExecutor;
|
import org.xgqy.survival.command.KillCommandExecutor;
|
||||||
import org.xgqy.survival.command.LandCommandExecutor;
|
import org.xgqy.survival.command.LandCommandExecutor;
|
||||||
import org.xgqy.survival.command.LoginCommandExecutor;
|
import org.xgqy.survival.command.LoginCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.MessageBoardCommandExecutor;
|
||||||
import org.xgqy.survival.command.NoticeCommandExecutor;
|
import org.xgqy.survival.command.NoticeCommandExecutor;
|
||||||
import org.xgqy.survival.command.PartyCommandExecutor;
|
import org.xgqy.survival.command.PartyCommandExecutor;
|
||||||
import org.xgqy.survival.command.PcCommandExecutor;
|
import org.xgqy.survival.command.PcCommandExecutor;
|
||||||
import org.xgqy.survival.command.PointCommandExecutor;
|
import org.xgqy.survival.command.PointCommandExecutor;
|
||||||
import org.xgqy.survival.command.PvpCommandExecutor;
|
import org.xgqy.survival.command.PvpCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.RankCommandExecutor;
|
||||||
import org.xgqy.survival.command.RegCommandExecutor;
|
import org.xgqy.survival.command.RegCommandExecutor;
|
||||||
import org.xgqy.survival.command.ReportCommandExecutor;
|
import org.xgqy.survival.command.ReportCommandExecutor;
|
||||||
import org.xgqy.survival.command.SetTagCommandExecutor;
|
import org.xgqy.survival.command.SetTagCommandExecutor;
|
||||||
@@ -37,6 +39,7 @@ import org.xgqy.survival.event.ForceSurvival;
|
|||||||
import org.xgqy.survival.event.JoinEvent;
|
import org.xgqy.survival.event.JoinEvent;
|
||||||
import org.xgqy.survival.event.LandEvent;
|
import org.xgqy.survival.event.LandEvent;
|
||||||
import org.xgqy.survival.event.LoginEvent;
|
import org.xgqy.survival.event.LoginEvent;
|
||||||
|
import org.xgqy.survival.event.RankAdd;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -60,6 +63,7 @@ public final class Survival extends JavaPlugin {
|
|||||||
public Map<Player, String> banreason = new HashMap<>();
|
public Map<Player, String> banreason = new HashMap<>();
|
||||||
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
|
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
|
||||||
// Teleport
|
// Teleport
|
||||||
|
private RankAdd contributionManager;
|
||||||
public Map<Player, Player> teleport = new HashMap<>();
|
public Map<Player, Player> teleport = new HashMap<>();
|
||||||
public Map<Player, Player> Ateleport = new HashMap<>();
|
public Map<Player, Player> Ateleport = new HashMap<>();
|
||||||
public Map<Player, Location> teleportp = new HashMap<>();
|
public Map<Player, Location> teleportp = new HashMap<>();
|
||||||
@@ -151,7 +155,6 @@ public final class Survival extends JavaPlugin {
|
|||||||
// 信用分数据文件相关
|
// 信用分数据文件相关
|
||||||
private File ppointFile;
|
private File ppointFile;
|
||||||
private FileConfiguration ppointConfig;
|
private FileConfiguration ppointConfig;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// 初始化账号管理器
|
// 初始化账号管理器
|
||||||
@@ -288,15 +291,27 @@ public final class Survival extends JavaPlugin {
|
|||||||
getCommand("land").setExecutor(new LandCommandExecutor(this));
|
getCommand("land").setExecutor(new LandCommandExecutor(this));
|
||||||
getCommand("qd").setExecutor(new LandCommandExecutor(this));
|
getCommand("qd").setExecutor(new LandCommandExecutor(this));
|
||||||
getCommand("notice").setExecutor(new NoticeCommandExecutor(this));
|
getCommand("notice").setExecutor(new NoticeCommandExecutor(this));
|
||||||
|
MessageBoardCommandExecutor mbExecutor = new MessageBoardCommandExecutor(this);
|
||||||
|
this.getCommand("messageboard").setExecutor(mbExecutor);
|
||||||
|
this.getCommand("mb").setExecutor(mbExecutor); // 别名支持
|
||||||
|
this.contributionManager = new RankAdd(this);
|
||||||
|
|
||||||
|
// 注册排行榜命令
|
||||||
|
RankCommandExecutor rankExecutor = new RankCommandExecutor(this);
|
||||||
|
this.getCommand("rank").setExecutor(rankExecutor);
|
||||||
CommandTabExecutor tabExecutor = new CommandTabExecutor();
|
CommandTabExecutor tabExecutor = new CommandTabExecutor();
|
||||||
registerTabCompleter(tabExecutor);
|
registerTabCompleter(tabExecutor);
|
||||||
}
|
}
|
||||||
|
public RankAdd getContributionManager() {
|
||||||
|
return contributionManager;
|
||||||
|
}
|
||||||
private void registerTabCompleter(CommandTabExecutor completer) {
|
private void registerTabCompleter(CommandTabExecutor completer) {
|
||||||
// 所有需要补全的命令列表
|
// 所有需要补全的命令列表
|
||||||
List<String> commands = Arrays.asList(
|
List<String> commands = Arrays.asList(
|
||||||
"handle", "tpacc", "friend", "party", "settag", "teleport",
|
"handle", "tpacc", "friend", "party", "settag", "teleport",
|
||||||
"point", "land", "pvp", "help", "tag", "hub", "home",
|
"point", "land", "pvp", "help", "tag", "hub", "home",
|
||||||
"qd", "reg", "login", "msg", "inventory", "notice"
|
"qd", "reg", "login", "msg", "inventory", "notice","messageboard","mb",
|
||||||
|
"rank"
|
||||||
);
|
);
|
||||||
|
|
||||||
for (String cmd : commands) {
|
for (String cmd : commands) {
|
||||||
@@ -311,6 +326,11 @@ public final class Survival extends JavaPlugin {
|
|||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
// 保存玩家标签数据
|
// 保存玩家标签数据
|
||||||
playerTags.saveTags();
|
playerTags.saveTags();
|
||||||
|
for(Player p : Bukkit.getOnlinePlayers()){
|
||||||
|
p.kickPlayer(ChatColor.GREEN+"========================================================\n"+
|
||||||
|
ChatColor.RED+"服务器重启,请稍等 1 - 3 分钟后再次加入\n"+
|
||||||
|
ChatColor.GREEN+"========================================================");
|
||||||
|
}
|
||||||
|
|
||||||
// 保存信用分数据
|
// 保存信用分数据
|
||||||
savePpoint();
|
savePpoint();
|
||||||
|
|||||||
@@ -14,17 +14,9 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一命令补全器:整合所有命令的自动补全逻辑
|
|
||||||
*/
|
|
||||||
public class CommandTabExecutor implements TabCompleter {
|
public class CommandTabExecutor implements TabCompleter {
|
||||||
|
|
||||||
// 支持的颜色列表(用于 settag 命令)
|
|
||||||
private static final Map<String, String> COLOR_MAP = new HashMap<>();
|
private static final Map<String, String> COLOR_MAP = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// 初始化颜色映射(与 SetTagCommandExecutor 对应)
|
|
||||||
COLOR_MAP.put("black", "");
|
COLOR_MAP.put("black", "");
|
||||||
COLOR_MAP.put("dark_blue", "");
|
COLOR_MAP.put("dark_blue", "");
|
||||||
COLOR_MAP.put("dark_green", "");
|
COLOR_MAP.put("dark_green", "");
|
||||||
@@ -48,8 +40,6 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
String cmdName = command.getName().toLowerCase();
|
String cmdName = command.getName().toLowerCase();
|
||||||
|
|
||||||
// 根据命令名称分发补全逻辑
|
|
||||||
switch (cmdName) {
|
switch (cmdName) {
|
||||||
case "handle":
|
case "handle":
|
||||||
completions = handleTabComplete(args);
|
completions = handleTabComplete(args);
|
||||||
@@ -86,23 +76,43 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
case "notice":
|
case "notice":
|
||||||
completions = simpleCommandTabComplete(sender, cmdName, args);
|
completions = simpleCommandTabComplete(sender, cmdName, args);
|
||||||
break;
|
break;
|
||||||
|
case "mb":
|
||||||
|
case "messageboard":
|
||||||
|
completions = mbTabCompelete(args);
|
||||||
|
case "rank":
|
||||||
|
completions = rankTabCompelete((Player) sender,args);
|
||||||
default:
|
default:
|
||||||
// 未知命令返回空
|
|
||||||
completions.clear();
|
completions.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
private List<String> rankTabCompelete(Player sender,String[] args){
|
||||||
// ------------------------------ 各命令补全逻辑 ------------------------------
|
List<String> completions = new ArrayList<>();
|
||||||
|
if(sender.isOp()){
|
||||||
/**
|
if(args.length == 1){
|
||||||
* /handle 命令补全(处理举报)
|
List<String> options = Arrays.asList("add","remove","all");
|
||||||
*/
|
completions = filterMatches(options,args[0]);
|
||||||
|
}
|
||||||
|
else if(args.length == 2 && (args[1] == "add" || args[1] == "remove")){
|
||||||
|
completions.add("<数值>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> mbTabCompelete(String[] args){
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if(args.length == 1){
|
||||||
|
List<String> options = Arrays.asList("add","list","support");
|
||||||
|
completions=filterMatches(options,args[0]);
|
||||||
|
}else{
|
||||||
|
completions.add("<内容>");
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
private List<String> handleTabComplete(String[] args) {
|
private List<String> handleTabComplete(String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
// 一级参数:no/point/12h/1d/3d/7d/14d/1mo/3mo/1y/ban
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = Arrays.asList(
|
List<String> options = Arrays.asList(
|
||||||
"no", "point", "12h", "1d", "3d", "7d", "14d",
|
"no", "point", "12h", "1d", "3d", "7d", "14d",
|
||||||
@@ -110,107 +120,72 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
);
|
);
|
||||||
completions = filterMatches(options, args[0]);
|
completions = filterMatches(options, args[0]);
|
||||||
}
|
}
|
||||||
// 二级参数:ban 后补全天数提示
|
|
||||||
else if (args.length == 2 && "ban".equalsIgnoreCase(args[0])) {
|
else if (args.length == 2 && "ban".equalsIgnoreCase(args[0])) {
|
||||||
completions.add("<天数>");
|
completions.add("<天数>");
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /tpacc 命令补全(传送申请处理)
|
|
||||||
*/
|
|
||||||
private List<String> tpAccTabComplete(String[] args) {
|
private List<String> tpAccTabComplete(String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
// 一级参数:accept/deny/tome/detome
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = Arrays.asList("accept", "deny", "tome", "detome");
|
List<String> options = Arrays.asList("accept", "deny", "tome", "detome");
|
||||||
completions = filterMatches(options, args[0]);
|
completions = filterMatches(options, args[0]);
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /friend 命令补全(好友操作)
|
|
||||||
*/
|
|
||||||
private List<String> friendTabComplete(CommandSender sender, String[] args) {
|
private List<String> friendTabComplete(CommandSender sender, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player)) return completions;
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
|
||||||
// 一级参数:add/allow/deny/remove/list
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = Arrays.asList("add", "allow", "deny", "remove", "list");
|
List<String> options = Arrays.asList("add", "allow", "deny", "remove", "list");
|
||||||
completions = filterMatches(options, args[0]);
|
completions = filterMatches(options, args[0]);
|
||||||
}
|
}
|
||||||
// 二级参数:操作后补全在线玩家名
|
|
||||||
else if (args.length == 2 && Arrays.asList("add", "allow", "deny", "remove").contains(args[0].toLowerCase())) {
|
else if (args.length == 2 && Arrays.asList("add", "allow", "deny", "remove").contains(args[0].toLowerCase())) {
|
||||||
completions = getOnlinePlayers(args[1]);
|
completions = getOnlinePlayers(args[1]);
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /party 命令补全(队伍操作)
|
|
||||||
*/
|
|
||||||
private List<String> partyTabComplete(CommandSender sender, String[] args) {
|
private List<String> partyTabComplete(CommandSender sender, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player)) return completions;
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
|
||||||
// 一级参数:create/quit/join/list/kick/ban/disband/help
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = Arrays.asList(
|
List<String> options = Arrays.asList(
|
||||||
"create", "quit", "join", "list", "kick", "ban", "disband", "help"
|
"create", "quit", "join", "list", "kick", "ban", "disband", "help"
|
||||||
);
|
);
|
||||||
completions = filterMatches(options, args[0]);
|
completions = filterMatches(options, args[0]);
|
||||||
}
|
}
|
||||||
// 二级参数:join/kick/ban 后补全在线玩家名
|
|
||||||
else if (args.length == 2 && Arrays.asList("join", "kick", "ban").contains(args[0].toLowerCase())) {
|
else if (args.length == 2 && Arrays.asList("join", "kick", "ban").contains(args[0].toLowerCase())) {
|
||||||
completions = getOnlinePlayers(args[1]);
|
completions = getOnlinePlayers(args[1]);
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /settag 命令补全(设置标签)
|
|
||||||
*/
|
|
||||||
private List<String> setTagTabComplete(CommandSender sender, String[] args) {
|
private List<String> setTagTabComplete(CommandSender sender, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
// 权限校验:无权限则不显示补全
|
|
||||||
if (!sender.hasPermission("permission.settag")) return completions;
|
if (!sender.hasPermission("permission.settag")) return completions;
|
||||||
|
|
||||||
// 一级参数:在线玩家名
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
completions = getOnlinePlayers(args[1]);
|
completions = getOnlinePlayers(args[1]);
|
||||||
}
|
}
|
||||||
// 二级参数:add/remove/check
|
|
||||||
else if (args.length == 2) {
|
else if (args.length == 2) {
|
||||||
List<String> options = Arrays.asList("add", "remove", "check");
|
List<String> options = Arrays.asList("add", "remove", "check");
|
||||||
completions = filterMatches(options, args[1]);
|
completions = filterMatches(options, args[1]);
|
||||||
}
|
}
|
||||||
// 三级参数:add/check/remove 后补全标签内容
|
|
||||||
else if (args.length == 3) {
|
else if (args.length == 3) {
|
||||||
String op = args[1].toLowerCase();
|
String op = args[1].toLowerCase();
|
||||||
if (Arrays.asList("add", "check", "remove").contains(op)) {
|
if (Arrays.asList("add", "check", "remove").contains(op)) {
|
||||||
completions.add("<标签内容>");
|
completions.add("<标签内容>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 四级参数:add/check 后补全颜色
|
|
||||||
else if (args.length == 4) {
|
else if (args.length == 4) {
|
||||||
String op = args[1].toLowerCase();
|
String op = args[1].toLowerCase();
|
||||||
if (Arrays.asList("add", "check").contains(op)) {
|
if (Arrays.asList("add", "check").contains(op)) {
|
||||||
completions = filterMatches(new ArrayList<>(COLOR_MAP.keySet()), args[3]);
|
completions = filterMatches(new ArrayList<>(COLOR_MAP.keySet()), args[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 五级参数:add 后补全天数
|
|
||||||
else if (args.length == 5 && "add".equalsIgnoreCase(args[1])) {
|
else if (args.length == 5 && "add".equalsIgnoreCase(args[1])) {
|
||||||
completions.add("<天数(999=永久)>");
|
completions.add("<天数(999=永久)>");
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /teleport 命令补全(传送)
|
|
||||||
*/
|
|
||||||
private List<String> teleportTabComplete(CommandSender sender, String[] args) {
|
private List<String> teleportTabComplete(CommandSender sender, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player)) return completions;
|
if (!(sender instanceof Player)) return completions;
|
||||||
@@ -227,73 +202,48 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* /point /land 命令补全(信用分/领地操作)
|
|
||||||
*/
|
|
||||||
private List<String> pointLandTabComplete(CommandSender sender, String[] args) {
|
private List<String> pointLandTabComplete(CommandSender sender, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player)) return completions;
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
|
||||||
// 一级参数:在线玩家名
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
completions = getOnlinePlayers(args[0]);
|
completions = getOnlinePlayers(args[0]);
|
||||||
}
|
}
|
||||||
// 二级参数:set/add/remove
|
|
||||||
else if (args.length == 2) {
|
else if (args.length == 2) {
|
||||||
List<String> options = Arrays.asList("set", "add", "remove");
|
List<String> options = Arrays.asList("set", "add", "remove");
|
||||||
completions = filterMatches(options, args[1]);
|
completions = filterMatches(options, args[1]);
|
||||||
}
|
}
|
||||||
// 三级参数:数值提示
|
|
||||||
else if (args.length == 3) {
|
else if (args.length == 3) {
|
||||||
completions.add("<数值>");
|
completions.add("<数值>");
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 简单命令补全(pvp/help/tag/hub/selfkill/qd/reg/login/msg/inventory/notice)
|
|
||||||
*/
|
|
||||||
private List<String> simpleCommandTabComplete(CommandSender sender, String cmdName, String[] args) {
|
private List<String> simpleCommandTabComplete(CommandSender sender, String cmdName, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player)) return completions;
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
|
||||||
switch (cmdName) {
|
switch (cmdName) {
|
||||||
// /reg <密码> <确认密码>
|
|
||||||
case "reg":
|
case "reg":
|
||||||
if (args.length == 1) completions.add("<密码>");
|
if (args.length == 1) completions.add("<密码>");
|
||||||
else if (args.length == 2) completions.add("<确认密码>");
|
else if (args.length == 2) completions.add("<确认密码>");
|
||||||
break;
|
break;
|
||||||
// /login <密码>
|
|
||||||
case "login":
|
case "login":
|
||||||
if (args.length == 1) completions.add("<密码>");
|
if (args.length == 1) completions.add("<密码>");
|
||||||
break;
|
break;
|
||||||
// /msg <玩家名> <消息>
|
|
||||||
case "msg":
|
case "msg":
|
||||||
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
else if (args.length == 2) completions.add("<消息内容>");
|
else if (args.length == 2) completions.add("<消息内容>");
|
||||||
break;
|
break;
|
||||||
// /inventory <玩家名>
|
|
||||||
case "inventory":
|
case "inventory":
|
||||||
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
break;
|
break;
|
||||||
// /notice <玩家名>
|
|
||||||
case "notice":
|
case "notice":
|
||||||
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
break;
|
break;
|
||||||
// 无参数命令(pvp/help/tag/hub/selfkill/qd)
|
|
||||||
default:
|
default:
|
||||||
completions.clear();
|
completions.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ 通用工具方法 ------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取在线玩家名列表(支持前缀匹配)
|
|
||||||
*/
|
|
||||||
private static List<String> getOnlinePlayers(String partialName) {
|
private static List<String> getOnlinePlayers(String partialName) {
|
||||||
List<String> playerNames = new ArrayList<>();
|
List<String> playerNames = new ArrayList<>();
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
@@ -301,10 +251,6 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
}
|
}
|
||||||
return filterMatches(playerNames, partialName);
|
return filterMatches(playerNames, partialName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从候选列表中过滤出匹配输入前缀的选项(不区分大小写)
|
|
||||||
*/
|
|
||||||
private static List<String> filterMatches(List<String> candidates, String partial) {
|
private static List<String> filterMatches(List<String> candidates, String partial) {
|
||||||
String lowerPartial = partial.toLowerCase();
|
String lowerPartial = partial.toLowerCase();
|
||||||
return candidates.stream()
|
return candidates.stream()
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ public class HelpCommandExecutor implements CommandExecutor {
|
|||||||
sender.sendMessage(ChatColor.GREEN+"/party <参数> <参数> - 队伍");
|
sender.sendMessage(ChatColor.GREEN+"/party <参数> <参数> - 队伍");
|
||||||
sender.sendMessage(ChatColor.GREEN+"/friend <参数> <参数> - 好友");
|
sender.sendMessage(ChatColor.GREEN+"/friend <参数> <参数> - 好友");
|
||||||
sender.sendMessage(ChatColor.GREEN+"/msg <内容> - 私聊");
|
sender.sendMessage(ChatColor.GREEN+"/msg <内容> - 私聊");
|
||||||
sender.sendMessage(ChatColor.GREEN+"/selfkill - 自杀");
|
sender.sendMessage(ChatColor.GREEN+"/home - 回家");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/rank - 榜单");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/mb - 留言板");
|
||||||
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
|
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import org.bukkit.ChatColor;
|
|||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
@@ -87,21 +86,21 @@ public class KillCommandExecutor implements CommandExecutor, Listener {
|
|||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
// 仅玩家可使用
|
// 仅玩家可使用
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(ChatColor.RED + "❌ 无法对非玩家使用该命令!");
|
sender.sendMessage(ChatColor.RED + "X 无法对非玩家使用该命令!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 检查冷却状态
|
// 1. 检查冷却状态
|
||||||
if (isOnCooldown(player)) {
|
if (isOnCooldown(player)) {
|
||||||
long remainingSeconds = (cooldownMap.get(player) - System.currentTimeMillis()) / 1000;
|
long remainingSeconds = (cooldownMap.get(player) - System.currentTimeMillis()) / 1000;
|
||||||
player.sendMessage(ChatColor.RED + "⚠️ 命令冷却中!剩余 " + remainingSeconds + " 秒后可再次使用");
|
player.sendMessage(ChatColor.RED + "⚠ 命令冷却中!剩余 " + remainingSeconds + " 秒后可再次使用");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查经验是否足够
|
// 2. 检查经验是否足够
|
||||||
int totalExp = player.getTotalExperience();
|
int totalExp = player.getTotalExperience();
|
||||||
if (totalExp < EXP_COST) {
|
if (totalExp < EXP_COST) {
|
||||||
player.sendMessage(ChatColor.RED + "❌ 经验不足!使用该命令需消耗 " + EXP_COST + " 点经验,你当前仅拥有 " + totalExp + " 点");
|
player.sendMessage(ChatColor.RED + "X 经验不足!使用该命令需消耗 " + EXP_COST + " 点经验,你当前仅拥有 " + totalExp + " 点");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +109,10 @@ public class KillCommandExecutor implements CommandExecutor, Listener {
|
|||||||
Location startLoc = player.getLocation().clone(); // 记录起始位置(精确到坐标和角度)
|
Location startLoc = player.getLocation().clone(); // 记录起始位置(精确到坐标和角度)
|
||||||
TeleportData teleportData = new TeleportData(startLoc, EXP_COST);
|
TeleportData teleportData = new TeleportData(startLoc, EXP_COST);
|
||||||
teleportingPlayers.put(player, teleportData);
|
teleportingPlayers.put(player, teleportData);
|
||||||
|
|
||||||
// 4. 发送传送提示
|
// 4. 发送传送提示
|
||||||
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
player.sendMessage(ChatColor.YELLOW + "📡 自动回复活点启动!");
|
|
||||||
player.sendMessage(ChatColor.WHITE + "消耗:" + ChatColor.GREEN + EXP_COST + " 点经验");
|
player.sendMessage(ChatColor.WHITE + "消耗:" + ChatColor.GREEN + EXP_COST + " 点经验");
|
||||||
player.sendMessage(ChatColor.WHITE + "倒计时:" + ChatColor.RED + "5 秒(移动将取消传送)");
|
player.sendMessage(ChatColor.WHITE + "倒计时:" + ChatColor.RED + "5 秒");
|
||||||
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
|
|
||||||
// 5. 启动粒子效果任务(每0.1秒生成一圈粒子)
|
// 5. 启动粒子效果任务(每0.1秒生成一圈粒子)
|
||||||
@@ -144,36 +141,19 @@ public class KillCommandExecutor implements CommandExecutor, Listener {
|
|||||||
* 启动传送粒子效果(围绕玩家生成圆形蓝色粒子)
|
* 启动传送粒子效果(围绕玩家生成圆形蓝色粒子)
|
||||||
*/
|
*/
|
||||||
private void startParticleEffect(Player player, TeleportData data) {
|
private void startParticleEffect(Player player, TeleportData data) {
|
||||||
|
int k[] = {50};
|
||||||
BukkitTask particleTask = new BukkitRunnable() {
|
BukkitTask particleTask = new BukkitRunnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// 玩家已移动或传送结束,停止粒子
|
// 玩家已移动或传送结束,停止粒子
|
||||||
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
||||||
|
player.sendTitle(ChatColor.RED+"传送失败","剩余能量已退还",20,40,20);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
player.sendTitle(ChatColor.GREEN+"传送将在 "+String.format("%.1f",1.0*k[0]/10)+" 秒后完成",ChatColor.YELLOW+"已消耗能量: "+(String.format("%d",50-k[0])),0,20,0);
|
||||||
Location playerLoc = player.getLocation().add(0, PARTICLE_Y_OFFSET, 0);
|
k[0] -= 1;
|
||||||
World world = playerLoc.getWorld();
|
|
||||||
if (world == null) {
|
|
||||||
this.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成圆形粒子(围绕玩家)
|
|
||||||
for (int i = 0; i < PARTICLE_COUNT; i++) {
|
|
||||||
// 计算圆形坐标
|
|
||||||
double angle = 2 * Math.PI * i / PARTICLE_COUNT;
|
|
||||||
double x = Math.cos(angle) * PARTICLE_RADIUS;
|
|
||||||
double z = Math.sin(angle) * PARTICLE_RADIUS;
|
|
||||||
|
|
||||||
Location particleLoc = playerLoc.clone().add(x, 0, z);
|
|
||||||
// 配置红色stone粒子为蓝色(更美观)
|
|
||||||
Particle.DustOptions dustOptions = new Particle.DustOptions(PARTICLE_COLOR, 1.2f);
|
|
||||||
world.spawnParticle(TELEPORT_PARTICLE, particleLoc, 0, 0, 0, 0, 0, dustOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
playerLoc.subtract(0, PARTICLE_Y_OFFSET, 0); // 恢复玩家原位置(避免坐标偏移)
|
|
||||||
}
|
}
|
||||||
}.runTaskTimer(plugin, 0L, 2L); // 每2tick(0.1秒)执行一次
|
}.runTaskTimer(plugin, 0L, 2L); // 每2tick(0.1秒)执行一次
|
||||||
|
|
||||||
@@ -214,9 +194,9 @@ public class KillCommandExecutor implements CommandExecutor, Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. 发送传送成功提示
|
// 5. 发送传送成功提示
|
||||||
|
player.sendTitle(ChatColor.GREEN+"传送成功",ChatColor.GREEN+"你现在处于恢复期,30秒后才能再次使用",10,50,10);
|
||||||
player.sendMessage(ChatColor.GREEN + "=====================================");
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
player.sendMessage(ChatColor.GREEN + "🎉 传送成功!已返回活点");
|
player.sendMessage(ChatColor.GREEN + "传送成功!");
|
||||||
player.sendMessage(ChatColor.WHITE + "提示:" + ChatColor.GRAY + "30秒内无法再次使用该命令");
|
|
||||||
player.sendMessage(ChatColor.GREEN + "=====================================");
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
|
|
||||||
// 6. 设置30秒冷却
|
// 6. 设置30秒冷却
|
||||||
@@ -265,16 +245,28 @@ public class KillCommandExecutor implements CommandExecutor, Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 返还扣除的经验
|
// 2. 返还扣除的经验
|
||||||
player.setTotalExperience(player.getTotalExperience() + data.getDeductedExp());
|
//player.setTotalExperience(player.getTotalExperience() + data.getDeductedExp());
|
||||||
|
|
||||||
// 3. 发送取消提示
|
// 3. 发送取消提示
|
||||||
player.sendMessage(ChatColor.RED + "=====================================");
|
player.sendMessage(ChatColor.RED + "=====================================");
|
||||||
player.sendMessage(ChatColor.RED + " 传送已取消!");
|
player.sendMessage(ChatColor.RED + " 传送已取消!");
|
||||||
player.sendMessage(ChatColor.WHITE + "原因:" + ChatColor.RED + "传送期间移动");
|
|
||||||
player.sendMessage(ChatColor.WHITE + "提示:" + ChatColor.GRAY + "已返还 " + EXP_COST + " 点经验");
|
|
||||||
player.sendMessage(ChatColor.RED + "=====================================");
|
player.sendMessage(ChatColor.RED + "=====================================");
|
||||||
|
|
||||||
// 4. 清理传送状态
|
// 4. 清理传送状态
|
||||||
teleportingPlayers.remove(player);
|
teleportingPlayers.remove(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
[投票]
|
||||||
|
新功能投票
|
||||||
|
1.【偏实用性】榜单 0
|
||||||
|
2.【偏技术性】登录检测 0
|
||||||
|
3.【偏实用性】留言板 1
|
||||||
|
4.【偏实用性】玩家间购买 0
|
||||||
|
5.【偏技术性】完善并添加现有功能 0
|
||||||
|
6.【偏趣味性】打屁(限时功能)0
|
||||||
|
7.自己提建议___________
|
||||||
|
投票输入数字即可。(如果选择7,请附加一句建议)
|
||||||
|
结束时间: 2025-11-09 17:00
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MessageBoardCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
// 核心配置常量
|
||||||
|
private static final long COOLDOWN_DURATION = 24 * 60 * 60 * 1000; // 1天冷却(毫秒)
|
||||||
|
private static final int MAX_MESSAGE_LENGTH = 20; // 最大纯文本长度(不含颜色代码)
|
||||||
|
private static final int MESSAGES_PER_PAGE = 10; // 每页留言数
|
||||||
|
|
||||||
|
// 依赖对象
|
||||||
|
private final Survival plugin;
|
||||||
|
private final Gson gson;
|
||||||
|
private final File messagesFile;
|
||||||
|
private final File cooldownsFile;
|
||||||
|
|
||||||
|
// 内存数据缓存
|
||||||
|
private List<Message> messages;
|
||||||
|
private Map<String, PlayerCooldown> playerCooldowns;
|
||||||
|
|
||||||
|
// 留言类型枚举(区分普通留言和上报信息)
|
||||||
|
public enum MessageType {
|
||||||
|
ADD, SUPPORT
|
||||||
|
}
|
||||||
|
|
||||||
|
// 留言实体类(Gson序列化用)
|
||||||
|
public static class Message {
|
||||||
|
private String content; // 带颜色的内容
|
||||||
|
private String playerName; // 玩家名称
|
||||||
|
private String playerUuid; // 玩家UUID(防改名)
|
||||||
|
private long timestamp; // 时间戳
|
||||||
|
private MessageType type; // 留言类型
|
||||||
|
|
||||||
|
// Gson必需的默认构造函数
|
||||||
|
public Message() {}
|
||||||
|
|
||||||
|
public Message(String content, String playerName, String playerUuid, long timestamp, MessageType type) {
|
||||||
|
this.content = content;
|
||||||
|
this.playerName = playerName;
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter和Setter(Gson序列化必需)
|
||||||
|
public String getContent() { return content; }
|
||||||
|
public void setContent(String content) { this.content = content; }
|
||||||
|
public String getPlayerName() { return playerName; }
|
||||||
|
public void setPlayerName(String playerName) { this.playerName = playerName; }
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public long getTimestamp() { return timestamp; }
|
||||||
|
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
|
||||||
|
public MessageType getType() { return type; }
|
||||||
|
public void setType(MessageType type) { this.type = type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 玩家冷却实体类
|
||||||
|
public static class PlayerCooldown {
|
||||||
|
private String playerUuid;
|
||||||
|
private long lastAddTime; // 最后留言时间
|
||||||
|
private long lastSupportTime; // 最后上报时间
|
||||||
|
|
||||||
|
public PlayerCooldown() {}
|
||||||
|
|
||||||
|
public PlayerCooldown(String playerUuid, long lastAddTime, long lastSupportTime) {
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.lastAddTime = lastAddTime;
|
||||||
|
this.lastSupportTime = lastSupportTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter和Setter
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public long getLastAddTime() { return lastAddTime; }
|
||||||
|
public void setLastAddTime(long lastAddTime) { this.lastAddTime = lastAddTime; }
|
||||||
|
public long getLastSupportTime() { return lastSupportTime; }
|
||||||
|
public void setLastSupportTime(long lastSupportTime) { this.lastSupportTime = lastSupportTime; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBoardCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
// 初始化数据文件(存放在插件data目录)
|
||||||
|
this.messagesFile = new File(plugin.getDataFolder(), "messages.json");
|
||||||
|
this.cooldownsFile = new File(plugin.getDataFolder(), "playerCooldowns.json");
|
||||||
|
|
||||||
|
// 加载数据(文件不存在则自动创建)
|
||||||
|
this.messages = loadMessages();
|
||||||
|
this.playerCooldowns = loadPlayerCooldowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 仅允许玩家使用
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "❌ 仅玩家可使用该命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无参数时显示帮助和颜色提示
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendHelpMessage(player);
|
||||||
|
sendColorCodeTips(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子命令
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
switch (subCommand) {
|
||||||
|
case "add" -> handleAddCommand(player, args);
|
||||||
|
case "list" -> handleListCommand(player, args);
|
||||||
|
case "support" -> handleSupportCommand(player, args);
|
||||||
|
default -> {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 未知子命令!可用:add、list、support");
|
||||||
|
sendHelpMessage(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理添加留言命令 /messageboard add <留言>
|
||||||
|
*/
|
||||||
|
private void handleAddCommand(Player player, String[] args) {
|
||||||
|
// 检查参数完整性
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/messageboard add <留言内容>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接留言内容(支持空格)
|
||||||
|
StringBuilder contentSb = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
contentSb.append(args[i]).append(" ");
|
||||||
|
}
|
||||||
|
String rawContent = contentSb.toString().trim();
|
||||||
|
|
||||||
|
// 处理颜色代码(&替换为§)
|
||||||
|
String coloredContent = ChatColor.translateAlternateColorCodes('&', rawContent);
|
||||||
|
|
||||||
|
// 检查纯文本长度(排除颜色代码)
|
||||||
|
int plainLength = getPlainTextLength(rawContent);
|
||||||
|
if (plainLength > MAX_MESSAGE_LENGTH) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 留言过长!纯文本(不含颜色代码)最多" + MAX_MESSAGE_LENGTH + "字符,当前:" + plainLength);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查冷却时间
|
||||||
|
PlayerCooldown cooldown = getOrCreateCooldown(player);
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long remainingTime = cooldown.getLastAddTime() + COOLDOWN_DURATION - currentTime;
|
||||||
|
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 今日留言次数已用尽!请等待" + formatTime(remainingTime) + "后再试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存留言
|
||||||
|
Message message = new Message(
|
||||||
|
coloredContent,
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId().toString(),
|
||||||
|
currentTime,
|
||||||
|
MessageType.ADD
|
||||||
|
);
|
||||||
|
messages.add(message);
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
// 更新冷却时间
|
||||||
|
cooldown.setLastAddTime(currentTime);
|
||||||
|
savePlayerCooldowns();
|
||||||
|
|
||||||
|
// 反馈成功
|
||||||
|
player.sendMessage(ChatColor.GREEN + "✅ 留言发布成功!");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "内容:" + coloredContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理上报命令 /messageboard support <上报信息>
|
||||||
|
*/
|
||||||
|
private void handleSupportCommand(Player player, String[] args) {
|
||||||
|
// 检查参数完整性
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/messageboard support <上报信息>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接上报内容
|
||||||
|
StringBuilder contentSb = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
contentSb.append(args[i]).append(" ");
|
||||||
|
}
|
||||||
|
String rawContent = contentSb.toString().trim();
|
||||||
|
|
||||||
|
// 处理颜色代码
|
||||||
|
String coloredContent = ChatColor.translateAlternateColorCodes('&', rawContent);
|
||||||
|
|
||||||
|
// 检查冷却时间
|
||||||
|
PlayerCooldown cooldown = getOrCreateCooldown(player);
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long remainingTime = cooldown.getLastSupportTime() + COOLDOWN_DURATION - currentTime;
|
||||||
|
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 今日上报次数已用尽!请等待" + formatTime(remainingTime) + "后再试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存上报信息
|
||||||
|
Message message = new Message(
|
||||||
|
coloredContent,
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId().toString(),
|
||||||
|
currentTime,
|
||||||
|
MessageType.SUPPORT
|
||||||
|
);
|
||||||
|
messages.add(message);
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
// 更新冷却时间
|
||||||
|
cooldown.setLastSupportTime(currentTime);
|
||||||
|
savePlayerCooldowns();
|
||||||
|
|
||||||
|
// 反馈成功
|
||||||
|
player.sendMessage(ChatColor.GREEN + "✅ 上报成功!管理员会尽快处理");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "上报内容:" + coloredContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查看列表命令 /messageboard list [页码]
|
||||||
|
*/
|
||||||
|
private void handleListCommand(Player player, String[] args) {
|
||||||
|
// 筛选可见留言(普通玩家仅看ADD,管理员看所有)
|
||||||
|
boolean isAdmin = player.hasPermission("messageboard.admin") || player.isOp();
|
||||||
|
List<Message> visibleMessages = new ArrayList<>();
|
||||||
|
for (Message msg : messages) {
|
||||||
|
if (isAdmin || msg.getType() == MessageType.ADD) {
|
||||||
|
visibleMessages.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间倒序排序(最新在前)
|
||||||
|
visibleMessages.sort(Comparator.comparingLong(Message::getTimestamp).reversed());
|
||||||
|
|
||||||
|
// 解析页码(默认第1页)
|
||||||
|
int page = 1;
|
||||||
|
if (args.length >= 2) {
|
||||||
|
try {
|
||||||
|
page = Integer.parseInt(args[1]);
|
||||||
|
if (page < 1) page = 1;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 页码必须是正整数!默认显示第1页");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页计算
|
||||||
|
int totalMsg = visibleMessages.size();
|
||||||
|
int totalPages = (totalMsg + MESSAGES_PER_PAGE - 1) / MESSAGES_PER_PAGE; // 向上取整
|
||||||
|
|
||||||
|
// 检查页码有效性
|
||||||
|
if (page > totalPages) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 页码超出范围!总页数:" + totalPages);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 截取当前页数据
|
||||||
|
int start = (page - 1) * MESSAGES_PER_PAGE;
|
||||||
|
int end = Math.min(start + MESSAGES_PER_PAGE, totalMsg);
|
||||||
|
List<Message> pageMessages = visibleMessages.subList(start, end);
|
||||||
|
|
||||||
|
// 发送列表信息
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 留言列表(第" + page + "/" + totalPages + "页)=====");
|
||||||
|
if (pageMessages.isEmpty()) {
|
||||||
|
player.sendMessage(ChatColor.GRAY + "📭 当前页暂无留言");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < pageMessages.size(); i++) {
|
||||||
|
Message msg = pageMessages.get(i);
|
||||||
|
String typeTag = msg.getType() == MessageType.SUPPORT ? ChatColor.RED + "[上报] " : "";
|
||||||
|
String line = ChatColor.WHITE + String.format("%d",(start + i + 1)) + ". " + typeTag + msg.getContent()
|
||||||
|
+ ChatColor.GRAY + " - " + msg.getPlayerName();
|
||||||
|
player.sendMessage(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.sendMessage(ChatColor.GREEN + "==========================");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "💡 提示:输入 /messageboard list <页码> 查看其他页");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送帮助信息
|
||||||
|
*/
|
||||||
|
private void sendHelpMessage(Player player) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 留言板命令帮助 =====");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb add <留言> - 发布公开留言(1天1次,纯文本≤20字符)");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb list [页码] - 查看留言列表(1页10条)");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb support <信息> - 上报问题(1天1次,仅管理员可见)");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "==========================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送颜色代码使用提示
|
||||||
|
*/
|
||||||
|
private void sendColorCodeTips(Player player) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 可用颜色代码 =====");
|
||||||
|
player.sendMessage(ChatColor.BLACK + "&0 黑色 " + ChatColor.DARK_BLUE + "&1 深蓝色 " + ChatColor.DARK_GREEN + "&2 深绿色");
|
||||||
|
player.sendMessage(ChatColor.DARK_AQUA + "&3 深青色 " + ChatColor.DARK_RED + "&4 深红色 " + ChatColor.DARK_PURPLE + "&5 深紫色");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "&6 金色 " + ChatColor.GRAY + "&7 灰色 " + ChatColor.DARK_GRAY + "&8 深灰色");
|
||||||
|
player.sendMessage(ChatColor.BLUE + "&9 蓝色 " + ChatColor.GREEN + "&a 绿色 " + ChatColor.AQUA + "&b 青色");
|
||||||
|
player.sendMessage(ChatColor.RED + "&c 红色 " + ChatColor.LIGHT_PURPLE + "&d 紫色 " + ChatColor.YELLOW + "&e 黄色");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "&f 白色");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=======================");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "💡 提示:颜色代码(&+字符)不计入20字符限制");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算纯文本长度(移除所有颜色代码)
|
||||||
|
*/
|
||||||
|
private int getPlainTextLength(String rawContent) {
|
||||||
|
// 正则替换所有 & 后跟一个字符(颜色代码)为空白
|
||||||
|
return rawContent.replaceAll("&.", "").length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间(毫秒转天时分秒)
|
||||||
|
*/
|
||||||
|
private String formatTime(long ms) {
|
||||||
|
long days = ms / (24 * 60 * 60 * 1000);
|
||||||
|
ms %= 24 * 60 * 60 * 1000;
|
||||||
|
long hours = ms / (60 * 60 * 1000);
|
||||||
|
ms %= 60 * 60 * 1000;
|
||||||
|
long minutes = ms / (60 * 1000);
|
||||||
|
long seconds = ms / 1000;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (days > 0) sb.append(days).append("天");
|
||||||
|
if (hours > 0) sb.append(hours).append("时");
|
||||||
|
if (minutes > 0) sb.append(minutes).append("分");
|
||||||
|
if (seconds > 0) sb.append(seconds).append("秒");
|
||||||
|
return sb.toString().isEmpty() ? "0秒" : sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或创建玩家冷却数据
|
||||||
|
*/
|
||||||
|
private PlayerCooldown getOrCreateCooldown(Player player) {
|
||||||
|
String uuid = player.getUniqueId().toString();
|
||||||
|
return playerCooldowns.computeIfAbsent(uuid, k -> new PlayerCooldown(uuid, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载留言数据(从JSON文件)
|
||||||
|
*/
|
||||||
|
private List<Message> loadMessages() {
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
createFile(messagesFile);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(messagesFile)) {
|
||||||
|
Type listType = new TypeToken<ArrayList<Message>>() {}.getType();
|
||||||
|
List<Message> loaded = gson.fromJson(reader, listType);
|
||||||
|
return loaded != null ? loaded : new ArrayList<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载留言文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存留言数据(到JSON文件)
|
||||||
|
*/
|
||||||
|
private void saveMessages() {
|
||||||
|
try (FileWriter writer = new FileWriter(messagesFile)) {
|
||||||
|
gson.toJson(messages, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存留言文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载玩家冷却数据
|
||||||
|
*/
|
||||||
|
private Map<String, PlayerCooldown> loadPlayerCooldowns() {
|
||||||
|
if (!cooldownsFile.exists()) {
|
||||||
|
createFile(cooldownsFile);
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(cooldownsFile)) {
|
||||||
|
Type mapType = new TypeToken<HashMap<String, PlayerCooldown>>() {}.getType();
|
||||||
|
Map<String, PlayerCooldown> loaded = gson.fromJson(reader, mapType);
|
||||||
|
return loaded != null ? loaded : new HashMap<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载冷却文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存玩家冷却数据
|
||||||
|
*/
|
||||||
|
private void savePlayerCooldowns() {
|
||||||
|
try (FileWriter writer = new FileWriter(cooldownsFile)) {
|
||||||
|
gson.toJson(playerCooldowns, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存冷却文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文件(自动创建父目录)
|
||||||
|
*/
|
||||||
|
private void createFile(File file) {
|
||||||
|
try {
|
||||||
|
file.getParentFile().mkdirs(); // 创建父目录
|
||||||
|
file.createNewFile(); // 创建文件
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("创建文件失败:" + file.getPath() + " - " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
src/main/java/org/xgqy/survival/command/RankCommandExecutor.java
Normal file
216
src/main/java/org/xgqy/survival/command/RankCommandExecutor.java
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
import org.xgqy.survival.event.RankAdd;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RankCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 关键修正1:成员变量类型改为 ContributionManager(之前错写为 RankAdd)
|
||||||
|
private final RankAdd contributionManager;
|
||||||
|
private static final String ADMIN_PERMISSION = "rank.admin";
|
||||||
|
private static final int TOP_LIMIT = 10;
|
||||||
|
|
||||||
|
public RankCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// 关键修正2:获取正确的 ContributionManager 实例(依赖主类的 getContributionManager 方法)
|
||||||
|
this.contributionManager = plugin.getContributionManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendTop10Rank(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
switch (subCommand) {
|
||||||
|
case "add":
|
||||||
|
handleAddScore(sender, args);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
handleRemoveScore(sender, args);
|
||||||
|
break;
|
||||||
|
case "all":
|
||||||
|
sendAllRank(sender);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sender.sendMessage(ChatColor.RED + "未知子命令!可用:add、remove、all");
|
||||||
|
sendHelpMessage(sender);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private void sendTop10Rank(CommandSender sender) {
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "===== 贡献值排行榜(前10名)=====");
|
||||||
|
// 关键修正3:使用 ContributionManager 的正确内部类 PlayerContribution
|
||||||
|
List<RankAdd.PlayerContribution> topList = contributionManager.getTopContributors();
|
||||||
|
|
||||||
|
for (int i = 0; i < TOP_LIMIT; i++) {
|
||||||
|
int rank = i + 1;
|
||||||
|
if (i < topList.size()) {
|
||||||
|
RankAdd.PlayerContribution data = topList.get(i);
|
||||||
|
String rankPrefix = switch (rank) {
|
||||||
|
case 1 -> ChatColor.RED + "🥇 " + ChatColor.WHITE;
|
||||||
|
case 2 -> ChatColor.GRAY + "🥈 " + ChatColor.WHITE;
|
||||||
|
case 3 -> ChatColor.YELLOW + "🥉 " + ChatColor.WHITE;
|
||||||
|
default -> ChatColor.WHITE + "[" + rank + "] ";
|
||||||
|
};
|
||||||
|
// 修正4:现在能正确识别 getPlayerName() 和 getTotalContribution()(因为 data 是正确的类型)
|
||||||
|
String line = rankPrefix + data.getPlayerName() + ChatColor.GRAY + " - " + ChatColor.YELLOW + data.getTotalContribution() + "点";
|
||||||
|
sender.sendMessage(line);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "[" + rank + "] " + ChatColor.GRAY + "暂无 - 暂无");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "==============================");
|
||||||
|
if (sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "管理员提示:使用 /rank all 查看完整列表");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送所有玩家贡献值完整列表
|
||||||
|
*/
|
||||||
|
private void sendAllRank(CommandSender sender) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可查看完整列表");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "===== 全服贡献值完整列表 =====");
|
||||||
|
List<RankAdd.PlayerContribution> allList = contributionManager.getAllContributions();
|
||||||
|
|
||||||
|
if (allList.isEmpty()) {
|
||||||
|
sender.sendMessage(ChatColor.GRAY + "暂无任何玩家贡献值数据");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < allList.size(); i++) {
|
||||||
|
RankAdd.PlayerContribution data = allList.get(i);
|
||||||
|
int rank = i + 1;
|
||||||
|
String line = ChatColor.WHITE + "[" + rank + "] " + data.getPlayerName() +
|
||||||
|
ChatColor.GRAY + " - " + ChatColor.YELLOW + data.getTotalContribution() + "点";
|
||||||
|
sender.sendMessage(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "==============================");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "总计 " + allList.size() + " 名玩家有贡献值记录");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员添加玩家贡献值
|
||||||
|
*/
|
||||||
|
private void handleAddScore(CommandSender sender, String[] args) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可操作");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/rank add <玩家名> <分数>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = args[1];
|
||||||
|
int score;
|
||||||
|
try {
|
||||||
|
score = Integer.parseInt(args[2]);
|
||||||
|
if (score <= 0) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须为正整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须是整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "未找到玩家 " + playerName + "(从未加入过服务器)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = "管理员[" + sender.getName() + "]手动添加分数";
|
||||||
|
// 修正5:调用 RankAdd 的正确方法,内部类枚举也需正确引用
|
||||||
|
int newScore = contributionManager.addCustomContribution(
|
||||||
|
targetPlayer.getPlayer(),
|
||||||
|
score,
|
||||||
|
RankAdd.ContributionType.TASK_MAIN, // 正确引用枚举
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "操作成功!");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "玩家:" + targetPlayer.getName());
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "添加分数:" + ChatColor.YELLOW + score + "点");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "当前总贡献:" + ChatColor.YELLOW + newScore + "点");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员减少玩家贡献值
|
||||||
|
*/
|
||||||
|
private void handleRemoveScore(CommandSender sender, String[] args) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可操作");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/rank remove <玩家名> <分数>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = args[1];
|
||||||
|
int score;
|
||||||
|
try {
|
||||||
|
score = Integer.parseInt(args[2]);
|
||||||
|
if (score <= 0) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须为正整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须是整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "未找到玩家 " + playerName + "(从未加入过服务器)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = "管理员[" + sender.getName() + "]手动扣除分数";
|
||||||
|
int newScore = contributionManager.reduceContribution(
|
||||||
|
targetPlayer.getPlayer(),
|
||||||
|
score,
|
||||||
|
RankAdd.ContributionType.TASK_MAIN,
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "操作成功!");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "玩家:" + targetPlayer.getName());
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "扣除分数:" + ChatColor.RED + score + "点");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "当前总贡献:" + ChatColor.YELLOW + newScore + "点");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送帮助信息
|
||||||
|
*/
|
||||||
|
private void sendHelpMessage(CommandSender sender) {
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "===== 贡献值排行榜命令帮助 =====");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank - 查看前10名贡献值排行榜");
|
||||||
|
if (sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank add <玩家名> <分数> - 为玩家添加贡献值");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank remove <玩家名> <分数> - 为玩家扣除贡献值");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank all - 查看全服玩家贡献值完整列表");
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "==============================");
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/java/org/xgqy/survival/event/FartExecutor.java
Normal file
7
src/main/java/org/xgqy/survival/event/FartExecutor.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
public class FartExecutor implements Listener {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ public class ForceSurvival implements Listener {
|
|||||||
private void gamemodechange(PlayerGameModeChangeEvent event){
|
private void gamemodechange(PlayerGameModeChangeEvent event){
|
||||||
if(event.getNewGameMode() != GameMode.SURVIVAL){
|
if(event.getNewGameMode() != GameMode.SURVIVAL){
|
||||||
event.getPlayer().setGameMode(GameMode.SURVIVAL);
|
event.getPlayer().setGameMode(GameMode.SURVIVAL);
|
||||||
|
event.setCancelled(true);
|
||||||
event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式");
|
event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.block.BlockBreakEvent;
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
@@ -35,6 +37,11 @@ public class LoginEvent implements Listener {
|
|||||||
private final Survival plugin;
|
private final Survival plugin;
|
||||||
// 存储未登录玩家的初始位置(登录后恢复用)
|
// 存储未登录玩家的初始位置(登录后恢复用)
|
||||||
private final Map<Player, Location> initialLocations = new HashMap<>();
|
private final Map<Player, Location> initialLocations = new HashMap<>();
|
||||||
|
// 记录未登录玩家是否已落地(用于死亡保护逻辑)
|
||||||
|
private final Map<Player, Boolean> unloggedPlayerLanded = new HashMap<>();
|
||||||
|
// 落地判定参数(可根据服务器世界设置调整)
|
||||||
|
private static final double LAND_Y_THRESHOLD = 64; // 落地Y轴阈值(默认世界表面平均高度)
|
||||||
|
private static final int HIGH_ALTITUDE_SAFE = 200; // 高危高空判定(超过此高度强制使用重生点)
|
||||||
|
|
||||||
public LoginEvent(Survival plugin) {
|
public LoginEvent(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@@ -45,13 +52,17 @@ public class LoginEvent implements Listener {
|
|||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
Location initialLoc = player.getLocation().clone();
|
Location initialLoc = player.getLocation().clone();
|
||||||
initialLocations.put(player, initialLoc); // 记录初始位置
|
initialLocations.put(player, initialLoc); // 记录初始位置
|
||||||
|
unloggedPlayerLanded.put(player, false); // 初始化未落地状态
|
||||||
// 初始化未登录隔离状态:隐身+高处传送+致盲
|
// 初始化未登录隔离状态:隐身+高处传送+致盲
|
||||||
setupUnloggedState(player);
|
setupUnloggedState(player);
|
||||||
|
|
||||||
|
// 新玩家自动添加"小萌新"标签(30天)
|
||||||
if(!plugin.getAccountManager().isRegistered(player.getUniqueId())){
|
if(!plugin.getAccountManager().isRegistered(player.getUniqueId())){
|
||||||
if(!plugin.getPlayerTags().getTags(player).contains("小萌新")){
|
if(!plugin.getPlayerTags().getTags(player).contains("小萌新")){
|
||||||
plugin.getPlayerTags().addTag(player,"小萌新",30);
|
plugin.getPlayerTags().addTag(player,"小萌新",30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录提示定时器(每秒发送一次,登录后停止)
|
// 登录提示定时器(每秒发送一次,登录后停止)
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -59,32 +70,27 @@ public class LoginEvent implements Listener {
|
|||||||
if (plugin.getAccountManager().isLoggedIn(player)) {
|
if (plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
// 登录成功,恢复正常状态
|
// 登录成功,恢复正常状态
|
||||||
restoreLoggedState(player);
|
restoreLoggedState(player);
|
||||||
initialLocations.remove(player); // 移除缓存的初始位置
|
initialLocations.remove(player);
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 发送登录/注册提示
|
// 发送登录/注册提示(修复注册命令格式错误)
|
||||||
if (plugin.getAccountManager().isRegistered(player.getUniqueId())) {
|
if (plugin.getAccountManager().isRegistered(player.getUniqueId())) {
|
||||||
player.sendMessage("§e欢迎回来! 请使用 /login <密码> 登录");
|
player.sendMessage("§e欢迎回来! 请使用 §a/login <密码> §e登录");
|
||||||
player.sendTitle("§b星阁钱语", "§e请输入 /login <密码> 登录", 0, 100, 0);
|
player.sendTitle("§b星阁钱语", "§e请输入 /login <密码> 登录", 0, 100, 0);
|
||||||
} else {
|
} else {
|
||||||
BaseComponent[] message = new ComponentBuilder()
|
BaseComponent[] termsMsg = new ComponentBuilder()
|
||||||
.append(new ComponentBuilder(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)")
|
.append(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)")
|
||||||
// 设置点击事件:复制链接到剪贴板
|
|
||||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html"))
|
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html"))
|
||||||
// 设置悬浮事件:鼠标悬停时显示的文字
|
|
||||||
.event(new HoverEvent(
|
.event(new HoverEvent(
|
||||||
HoverEvent.Action.SHOW_TEXT, // 悬浮动作:显示文本
|
HoverEvent.Action.SHOW_TEXT,
|
||||||
// 悬浮文字内容(可带颜色)
|
new ComponentBuilder(ChatColor.GRAY + "https://starpavilion.xyz/terms.html").create()
|
||||||
new ComponentBuilder()
|
|
||||||
.append(ChatColor.GRAY+"https://starpavilion.xyz/terms.html")
|
|
||||||
.create()
|
|
||||||
))
|
))
|
||||||
.create())
|
|
||||||
.create();
|
.create();
|
||||||
player.spigot().sendMessage(message);
|
player.spigot().sendMessage(termsMsg);
|
||||||
player.sendMessage("§e欢迎来到服务器! 请使用 /reg <密码> <确认密码> 注册");
|
player.sendMessage("§e欢迎来到服务器! 请使用 §a/reg <密码> <确认密码> <确认码> §e注册");
|
||||||
player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <密码> <确认码> 注册", 0, 100, 0);
|
player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <确认密码> <确认码>", 0, 100, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.runTaskTimer(plugin, 60, 60); // 1秒发送一次
|
}.runTaskTimer(plugin, 60, 60); // 1秒发送一次
|
||||||
@@ -95,7 +101,8 @@ public class LoginEvent implements Listener {
|
|||||||
public void run() {
|
public void run() {
|
||||||
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
initialLocations.remove(player);
|
initialLocations.remove(player);
|
||||||
player.kickPlayer("§c登录超时");
|
unloggedPlayerLanded.remove(player);
|
||||||
|
player.kickPlayer("§c登录超时(60秒未操作)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.runTaskLater(plugin, 20 * 60);
|
}.runTaskLater(plugin, 20 * 60);
|
||||||
@@ -107,14 +114,37 @@ public class LoginEvent implements Listener {
|
|||||||
// 玩家退出时登出账号并清理缓存
|
// 玩家退出时登出账号并清理缓存
|
||||||
plugin.getAccountManager().logout(player);
|
plugin.getAccountManager().logout(player);
|
||||||
initialLocations.remove(player);
|
initialLocations.remove(player);
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ 核心拦截:阻止所有外界交互 ------------------------------
|
// ------------------------------ 核心拦截:阻止所有外界交互 ------------------------------
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerMove(PlayerMoveEvent event) {
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
Player player = event.getPlayer();
|
||||||
// 完全阻止移动(包括位置和视角旋转)
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
Location from = event.getFrom();
|
||||||
|
Location to = event.getTo();
|
||||||
|
if (to == null) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许垂直下落(保留Y轴移动),阻止水平移动(X/Z轴固定)和视角旋转(Yaw/Pitch固定)
|
||||||
|
Location newLoc = new Location(
|
||||||
|
player.getWorld(),
|
||||||
|
from.getX(), // 固定X轴
|
||||||
|
to.getY(), // 允许Y轴下落
|
||||||
|
from.getZ(), // 固定Z轴
|
||||||
|
from.getYaw(), // 固定视角水平旋转
|
||||||
|
from.getPitch() // 固定视角垂直旋转
|
||||||
|
);
|
||||||
|
event.setTo(newLoc);
|
||||||
|
|
||||||
|
// 落地检测:Y轴低于阈值 + 脚下有实体方块
|
||||||
|
if (newLoc.getY() <= LAND_Y_THRESHOLD && isBlockBelow(player)) {
|
||||||
|
unloggedPlayerLanded.put(player, true);
|
||||||
|
player.sendMessage("§a已落地!请尽快登录或注册");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,20 +152,20 @@ public class LoginEvent implements Listener {
|
|||||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
event.getPlayer().sendMessage("§c请先登录或注册");
|
event.getPlayer().sendMessage("§c请先登录或注册后再聊天");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
|
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
|
||||||
String command = event.getMessage().toLowerCase();
|
String command = event.getMessage().toLowerCase().trim();
|
||||||
// 仅允许登录/注册命令
|
// 仅允许登录/注册命令(支持带参数的完整命令)
|
||||||
if (command.startsWith("/reg ") || command.startsWith("/login ")) {
|
if (command.startsWith("/reg ") || command.startsWith("/login ")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
event.getPlayer().sendMessage("§c请先登录或注册");
|
event.getPlayer().sendMessage("§c请先登录或注册(仅允许 /reg /login 命令)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,18 +233,64 @@ public class LoginEvent implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ 状态管理工具方法 ------------------------------
|
// ------------------------------ 死亡保护:未落地前阻止伤害和死亡 ------------------------------
|
||||||
|
/**
|
||||||
|
* 拦截未登录玩家的伤害(主要是摔落伤害)
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamage(EntityDamageEvent event) {
|
||||||
|
if (event.getEntity() instanceof Player player) {
|
||||||
|
// 未登录且未落地时,拦截所有伤害并恢复生命值
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player) && !unloggedPlayerLanded.getOrDefault(player, false)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
// 强制恢复满生命值和饥饿值
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
player.setSaturation(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截未登录玩家的死亡事件
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||||
|
Player player = event.getEntity();
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
event.setDeathMessage("被资本做局了");
|
||||||
|
// 恢复状态,避免死亡惩罚
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
player.setSaturation(20);
|
||||||
|
player.getActivePotionEffects().forEach(effect ->
|
||||||
|
player.removePotionEffect(effect.getType())
|
||||||
|
);
|
||||||
|
player.sendMessage("§c未登录状态下无法死亡,请尽快登录或注册");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ 工具方法 ------------------------------
|
||||||
|
/**
|
||||||
|
* 检测玩家脚下是否有实体方块(落地判定辅助)
|
||||||
|
*/
|
||||||
|
private boolean isBlockBelow(Player player) {
|
||||||
|
Location below = player.getLocation().subtract(0, 1, 0);
|
||||||
|
return below.getBlock().getType().isSolid() && !below.getBlock().getType().isAir();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置未登录玩家的隔离状态:隐身+高处传送+致盲+交互阻断
|
* 设置未登录玩家的隔离状态:隐身+高处传送+致盲+交互阻断
|
||||||
*/
|
*/
|
||||||
private void setupUnloggedState(Player player) {
|
private void setupUnloggedState(Player player) {
|
||||||
// 1. 隐身(对其他玩家不可见)
|
// 1. 隐身(对其他玩家不可见)
|
||||||
player.setInvisible(true);
|
player.setInvisible(true);
|
||||||
// 2. 隐藏玩家列表名称(不在Tab列表显示)
|
// 2. 隐藏Tab列表名称
|
||||||
player.setPlayerListName("");
|
player.setPlayerListName("");
|
||||||
// 3. 禁用飞行控制(防止移动)
|
// 3. 禁用飞行(防止异常移动)
|
||||||
player.setAllowFlight(false);
|
player.setAllowFlight(false);
|
||||||
// 4. 传送至当前世界的安全高处(Y=255,避免掉落后受伤)
|
player.setFlying(false);
|
||||||
|
// 4. 传送至当前世界高空(Y=255,确保垂直下落路径安全)
|
||||||
Location highLoc = new Location(
|
Location highLoc = new Location(
|
||||||
player.getWorld(),
|
player.getWorld(),
|
||||||
player.getLocation().getX(),
|
player.getLocation().getX(),
|
||||||
@@ -222,47 +298,88 @@ public class LoginEvent implements Listener {
|
|||||||
player.getLocation().getZ()
|
player.getLocation().getZ()
|
||||||
);
|
);
|
||||||
player.teleport(highLoc);
|
player.teleport(highLoc);
|
||||||
// 6. 移除所有可能的干扰效果(保留致盲)
|
// 5. 施加致盲效果(增强隔离体验)
|
||||||
|
player.addPotionEffect(PotionEffectType.BLINDNESS.createEffect(Integer.MAX_VALUE, 0));
|
||||||
|
// 6. 移除所有干扰效果(保留致盲)
|
||||||
player.getActivePotionEffects().forEach(effect -> {
|
player.getActivePotionEffects().forEach(effect -> {
|
||||||
if (effect.getType() != PotionEffectType.BLINDNESS) {
|
if (effect.getType() != PotionEffectType.BLINDNESS) {
|
||||||
player.removePotionEffect(effect.getType());
|
player.removePotionEffect(effect.getType());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 7. 恢复满状态
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 恢复登录玩家的正常状态
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* 恢复登录玩家的正常状态
|
* 恢复登录玩家的正常状态
|
||||||
*/
|
*/
|
||||||
public void restoreLoggedState(Player player) {
|
public void restoreLoggedState(Player player) {
|
||||||
// 1. 先设置更长时间的无敌(10秒=200tick),确保覆盖坠落过程
|
// 1. 清理未登录状态缓存
|
||||||
player.setNoDamageTicks(200);
|
unloggedPlayerLanded.remove(player);
|
||||||
|
|
||||||
// 2. 恢复可见性
|
// 2. 恢复可见性
|
||||||
player.setInvisible(false);
|
player.setInvisible(false);
|
||||||
|
// 3. 恢复Tab列表名称(带标签)
|
||||||
// 3. 恢复玩家列表名称
|
|
||||||
List<String> tags = plugin.getPlayerTags().getTags(player);
|
List<String> tags = plugin.getPlayerTags().getTags(player);
|
||||||
int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player);
|
int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player);
|
||||||
|
// 安全处理标签索引(防止数组越界)
|
||||||
int validIndex = Math.max(0, Math.min(currentTagIndex, tags.size() - 1));
|
int validIndex = Math.max(0, Math.min(currentTagIndex, tags.size() - 1));
|
||||||
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(validIndex) + ChatColor.WHITE + "]" + player.getName());
|
String tag = tags.isEmpty() ? "默认" : tags.get(validIndex);
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tag + ChatColor.WHITE + "]" + player.getName());
|
||||||
// 4. 恢复飞行权限
|
// 4. 恢复飞行权限(默认禁用,根据服务器配置调整)
|
||||||
player.setAllowFlight(false);
|
player.setAllowFlight(false);
|
||||||
|
player.setFlying(false);
|
||||||
// 5. 移除致盲效果
|
// 5. 移除致盲效果
|
||||||
player.removePotionEffect(PotionEffectType.BLINDNESS);
|
player.removePotionEffect(PotionEffectType.BLINDNESS);
|
||||||
|
// 6. 传送回安全初始位置(避免高空危险)
|
||||||
// 6. 传送回初始位置(优先使用安全的重生点)
|
|
||||||
Location initialLoc = initialLocations.getOrDefault(player, player.getWorld().getSpawnLocation());
|
Location initialLoc = initialLocations.getOrDefault(player, player.getWorld().getSpawnLocation());
|
||||||
// 额外检查:如果初始位置Y坐标过低(比如高空),强制使用世界重生点
|
// 高危位置检测:超过200高度强制使用重生点
|
||||||
if (initialLoc.getY() > 200) { // 假设Y>200为危险高空
|
if (initialLoc.getY() > HIGH_ALTITUDE_SAFE) {
|
||||||
initialLoc = player.getWorld().getSpawnLocation();
|
initialLoc = player.getWorld().getSpawnLocation();
|
||||||
}
|
}
|
||||||
player.teleport(initialLoc);
|
player.teleport(initialLoc);
|
||||||
|
// 7. 恢复生存模式
|
||||||
// 7. 恢复游戏模式
|
|
||||||
player.setGameMode(GameMode.SURVIVAL);
|
player.setGameMode(GameMode.SURVIVAL);
|
||||||
|
// 8. 发送登录成功提示
|
||||||
|
player.sendMessage("§a登录成功!欢迎回到星阁钱语");
|
||||||
|
player.sendTitle("§b登录成功", "§a祝您游戏愉快", 10, 70, 20);
|
||||||
|
new BukkitRunnable(){
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
player.getScoreboard().getObjective("playtime").getScore(
|
||||||
|
player.getName()).setScore(player.getScoreboard().getObjective(
|
||||||
|
"playtime").getScore(player.getName()).getScore()+1);
|
||||||
|
// 游玩时间
|
||||||
|
// 贡献值
|
||||||
|
/*
|
||||||
|
贡献值计算方法:
|
||||||
|
1. 游玩满 1 min 增加 1
|
||||||
|
2. 游玩满 5 min 增加 1
|
||||||
|
3. 游玩满 10 min 增加 1
|
||||||
|
4. 游玩满 30 min 增加 2
|
||||||
|
5. 游玩满 1h 增加 2
|
||||||
|
6. 游玩大于 1h 增加 1 / 15 min
|
||||||
|
以上单位为 : 1d
|
||||||
|
7. 一个月以内登录超过 7 天增加 5
|
||||||
|
8. 一个月以内登录超过 14 天增加 10
|
||||||
|
9. 一个月以内登录超过 21 天增加 20
|
||||||
|
10. 一个月以内满勤 + 60
|
||||||
|
以上单位为 : 1mo
|
||||||
|
11. 一星期登录超过3天 + 2
|
||||||
|
12. 一星期登录超过5天 + 5
|
||||||
|
13. 一星期满勤 + 11
|
||||||
|
14. 其他活动增加
|
||||||
|
15. 充值{
|
||||||
|
无 *1.0
|
||||||
|
vip *1.1
|
||||||
|
vip+ *1.2
|
||||||
|
MVP *1.4
|
||||||
|
MVP+ *1.7
|
||||||
|
MVP++ *2.0
|
||||||
|
}
|
||||||
|
17. 充值点券 100点券(原始值,向下取整,不满100不算) = 1 贡献值(不叠加)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin,0,20L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
267
src/main/java/org/xgqy/survival/event/RankAdd.java
Normal file
267
src/main/java/org/xgqy/survival/event/RankAdd.java
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贡献值管理类:负责贡献值计算、存储、查询、修改
|
||||||
|
*/
|
||||||
|
public class RankAdd {
|
||||||
|
|
||||||
|
// 插件实例
|
||||||
|
private final Survival plugin;
|
||||||
|
// Gson 序列化工具(格式化输出)
|
||||||
|
private final Gson gson;
|
||||||
|
// 数据文件(存储所有玩家贡献值)
|
||||||
|
private final File contributionFile;
|
||||||
|
|
||||||
|
// 内存缓存:玩家UUID -> 贡献值数据
|
||||||
|
private Map<String, PlayerContribution> contributionMap;
|
||||||
|
|
||||||
|
// 贡献类型枚举(可扩展,每种类型对应默认贡献值)
|
||||||
|
public enum ContributionType {
|
||||||
|
TASK_MAIN(10), // 主线任务
|
||||||
|
TASK_DAILY(5), // 日常任务
|
||||||
|
DONATE_ITEM(3), // 捐赠物品
|
||||||
|
EVENT_PARTICIPATE(4),// 参与活动
|
||||||
|
BUILD_PUBLIC(8), // 建造公共设施
|
||||||
|
HELP_PLAYER(2); // 帮助其他玩家
|
||||||
|
|
||||||
|
private final int defaultValue; // 该类型默认贡献值
|
||||||
|
|
||||||
|
ContributionType(int defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 玩家贡献值实体类(存储单个玩家的贡献数据)
|
||||||
|
*/
|
||||||
|
public static class PlayerContribution {
|
||||||
|
private String playerUuid; // 玩家唯一标识(防改名)
|
||||||
|
private String playerName; // 玩家当前名称
|
||||||
|
private int totalContribution; // 总贡献值
|
||||||
|
private long lastUpdateTime; // 最后更新时间(毫秒)
|
||||||
|
private List<ContributionRecord> records; // 贡献记录(追溯用)
|
||||||
|
|
||||||
|
// Gson 序列化必需的默认构造函数
|
||||||
|
public PlayerContribution() {}
|
||||||
|
|
||||||
|
public PlayerContribution(String playerUuid, String playerName) {
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.playerName = playerName;
|
||||||
|
this.totalContribution = 0;
|
||||||
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
this.records = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter 和 Setter
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public String getPlayerName() { return playerName; }
|
||||||
|
public void setPlayerName(String playerName) { this.playerName = playerName; }
|
||||||
|
public int getTotalContribution() { return totalContribution; }
|
||||||
|
public void setTotalContribution(int totalContribution) { this.totalContribution = totalContribution; }
|
||||||
|
public long getLastUpdateTime() { return lastUpdateTime; }
|
||||||
|
public void setLastUpdateTime(long lastUpdateTime) { this.lastUpdateTime = lastUpdateTime; }
|
||||||
|
public List<ContributionRecord> getRecords() { return records; }
|
||||||
|
public void setRecords(List<ContributionRecord> records) { this.records = records; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贡献记录实体类(记录每次贡献值变动)
|
||||||
|
*/
|
||||||
|
public static class ContributionRecord {
|
||||||
|
private ContributionType type; // 贡献类型
|
||||||
|
private int value; // 变动值(正数=增加,负数=减少)
|
||||||
|
private long timestamp; // 变动时间(毫秒)
|
||||||
|
private String reason; // 变动原因(可选,如"完成主线任务:击败末影龙")
|
||||||
|
|
||||||
|
public ContributionRecord(ContributionType type, int value, String reason) {
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter
|
||||||
|
public ContributionType getType() { return type; }
|
||||||
|
public int getValue() { return value; }
|
||||||
|
public long getTimestamp() { return timestamp; }
|
||||||
|
public String getReason() { return reason; }
|
||||||
|
}
|
||||||
|
public List<PlayerContribution> getAllContributions() {
|
||||||
|
List<PlayerContribution> allData = new ArrayList<>(contributionMap.values());
|
||||||
|
allData.sort((d1, d2) -> Integer.compare(d2.getTotalContribution(), d1.getTotalContribution()));
|
||||||
|
return allData;
|
||||||
|
}
|
||||||
|
public RankAdd(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
this.contributionFile = new File(plugin.getDataFolder(), "contributions.json");
|
||||||
|
this.contributionMap = loadData();
|
||||||
|
}
|
||||||
|
public int addContribution(@NotNull Player player, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
return modifyContribution(player, type.getDefaultValue(), type, reason);
|
||||||
|
}
|
||||||
|
public int addCustomContribution(@Nullable Player player, int customValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
if (customValue <= 0) {
|
||||||
|
plugin.getLogger().warning("添加贡献值失败:自定义值必须为正数");
|
||||||
|
return player != null ? getContribution(player) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理离线玩家:通过OfflinePlayer的UUID获取数据
|
||||||
|
String uuidStr;
|
||||||
|
String playerName;
|
||||||
|
if (player != null) {
|
||||||
|
uuidStr = player.getUniqueId().toString();
|
||||||
|
playerName = player.getName();
|
||||||
|
} else {
|
||||||
|
// 若player为null(离线),需从贡献值缓存中查找(仅支持已有记录的离线玩家)
|
||||||
|
// 注:新增离线玩家需先通过UUID创建数据,此处仅处理已有记录的情况
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerContribution data = contributionMap.computeIfAbsent(uuidStr, k -> new PlayerContribution(uuidStr, playerName));
|
||||||
|
int newValue = Math.max(0, data.getTotalContribution() + customValue);
|
||||||
|
data.setTotalContribution(newValue);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
data.setPlayerName(playerName);
|
||||||
|
data.getRecords().add(new ContributionRecord(type, customValue, reason));
|
||||||
|
|
||||||
|
saveData();
|
||||||
|
plugin.getLogger().info("添加贡献值:" + playerName + " +" + customValue + ",原因:" + reason + ",当前:" + newValue);
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
public int reduceContribution(@NotNull Player player, int reduceValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
if (reduceValue <= 0) {
|
||||||
|
plugin.getLogger().warning("扣除玩家 " + player.getName() + " 贡献值失败:扣除值必须为正数");
|
||||||
|
return getContribution(player);
|
||||||
|
}
|
||||||
|
return modifyContribution(player, -reduceValue, type, reason);
|
||||||
|
}
|
||||||
|
public int getContribution(@NotNull Player player) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
return data.getTotalContribution();
|
||||||
|
}
|
||||||
|
public List<ContributionRecord> getContributionRecords(@NotNull Player player) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
List<ContributionRecord> records = data.getRecords();
|
||||||
|
// 倒序排列(最新记录在前)
|
||||||
|
records.sort((r1, r2) -> Long.compare(r2.getTimestamp(), r1.getTimestamp()));
|
||||||
|
// 只返回最近10条,避免数据过多
|
||||||
|
int end = Math.min(10, records.size());
|
||||||
|
return records.subList(0, end);
|
||||||
|
}
|
||||||
|
public void resetContribution(@NotNull Player player, @NotNull String reason) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
int oldValue = data.getTotalContribution();
|
||||||
|
data.setTotalContribution(0);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
// 添加重置记录(变动值为 -旧值)
|
||||||
|
data.getRecords().add(new ContributionRecord(ContributionType.TASK_MAIN, -oldValue, "重置贡献值:" + reason));
|
||||||
|
saveData(); // 保存到文件
|
||||||
|
plugin.getLogger().info("玩家 " + player.getName() + " 的贡献值已重置,原数值:" + oldValue + ",原因:" + reason);
|
||||||
|
}
|
||||||
|
public List<PlayerContribution> getTopContributors() {
|
||||||
|
List<PlayerContribution> allData = new ArrayList<>(contributionMap.values());
|
||||||
|
// 按总贡献值降序排序
|
||||||
|
allData.sort((d1, d2) -> Integer.compare(d2.getTotalContribution(), d1.getTotalContribution()));
|
||||||
|
// 返回前10名
|
||||||
|
int end = Math.min(10, allData.size());
|
||||||
|
return allData.subList(0, end);
|
||||||
|
}
|
||||||
|
private int modifyContribution(@NotNull Player player, int changeValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
|
||||||
|
// 计算新贡献值(最低为0,不允许负贡献)
|
||||||
|
int newValue = Math.max(0, data.getTotalContribution() + changeValue);
|
||||||
|
data.setTotalContribution(newValue);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
data.setPlayerName(player.getName()); // 同步最新玩家名称(防止改名后显示异常)
|
||||||
|
|
||||||
|
// 添加贡献记录
|
||||||
|
data.getRecords().add(new ContributionRecord(type, changeValue, reason));
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
saveData();
|
||||||
|
|
||||||
|
// 日志输出
|
||||||
|
String action = changeValue > 0 ? "添加" : "扣除";
|
||||||
|
plugin.getLogger().info(action + "玩家 " + player.getName() + " 贡献值:" + Math.abs(changeValue) +
|
||||||
|
",类型:" + type.name() + ",原因:" + reason + ",当前总贡献:" + newValue);
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
private PlayerContribution getOrCreatePlayerData(@NotNull Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
String uuidStr = uuid.toString();
|
||||||
|
|
||||||
|
// 从缓存获取,不存在则创建新数据
|
||||||
|
return contributionMap.computeIfAbsent(uuidStr, k -> new PlayerContribution(uuidStr, player.getName()));
|
||||||
|
}
|
||||||
|
private Map<String, PlayerContribution> loadData() {
|
||||||
|
// 文件不存在则创建空文件
|
||||||
|
if (!contributionFile.exists()) {
|
||||||
|
createFile(contributionFile);
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(contributionFile)) {
|
||||||
|
Type mapType = new TypeToken<HashMap<String, PlayerContribution>>() {}.getType();
|
||||||
|
Map<String, PlayerContribution> loadedMap = gson.fromJson(reader, mapType);
|
||||||
|
// 防止文件损坏导致的空指针
|
||||||
|
return loadedMap != null ? loadedMap : new HashMap<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载贡献值文件失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void saveData() {
|
||||||
|
try (FileWriter writer = new FileWriter(contributionFile)) {
|
||||||
|
gson.toJson(contributionMap, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存贡献值文件失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void createFile(File file) {
|
||||||
|
try {
|
||||||
|
file.getParentFile().mkdirs(); // 递归创建父目录(插件data目录)
|
||||||
|
file.createNewFile(); // 创建文件
|
||||||
|
plugin.getLogger().info("创建贡献值数据文件:" + file.getPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("创建贡献值文件失败!" + file.getPath() + " - " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int getContributionByName(String playerName) {
|
||||||
|
for (PlayerContribution data : contributionMap.values()) {
|
||||||
|
if (data.getPlayerName().equalsIgnoreCase(playerName)) {
|
||||||
|
return data.getTotalContribution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,13 @@ commands:
|
|||||||
notice:
|
notice:
|
||||||
description: notice a player
|
description: notice a player
|
||||||
usage: /<command> <player>
|
usage: /<command> <player>
|
||||||
|
messageboard:
|
||||||
|
description: 留言板命令
|
||||||
|
usage: /<command> [add|list|support]
|
||||||
|
aliases: [ mb ]
|
||||||
|
mb:
|
||||||
|
description: 留言板命令
|
||||||
|
usage: /<command> [add|list|support]
|
||||||
permissions:
|
permissions:
|
||||||
permission.settag:
|
permission.settag:
|
||||||
description: Allows setting player tags
|
description: Allows setting player tags
|
||||||
@@ -93,3 +100,6 @@ permissions:
|
|||||||
permission.handle:
|
permission.handle:
|
||||||
description: handle players reported
|
description: handle players reported
|
||||||
default: op
|
default: op
|
||||||
|
messageboard.admin:
|
||||||
|
description: admin
|
||||||
|
default: op
|
||||||
Reference in New Issue
Block a user