Rewritten
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 35s
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 35s
Command: - handle - report - Tab auto fill Added new Command: - notice - home
This commit is contained in:
@@ -33,6 +33,7 @@ import org.xgqy.survival.command.TpFinCommandExecutor;
|
|||||||
import org.xgqy.survival.command.msgCommandExecutor;
|
import org.xgqy.survival.command.msgCommandExecutor;
|
||||||
import org.xgqy.survival.event.ChatEvent;
|
import org.xgqy.survival.event.ChatEvent;
|
||||||
import org.xgqy.survival.event.ChooseTagEvent;
|
import org.xgqy.survival.event.ChooseTagEvent;
|
||||||
|
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;
|
||||||
@@ -176,6 +177,7 @@ public final class Survival extends JavaPlugin {
|
|||||||
msg.add(ChatColor.GREEN + "赞助我们? https://starpavilion.xyz/sponsor.html");
|
msg.add(ChatColor.GREEN + "赞助我们? https://starpavilion.xyz/sponsor.html");
|
||||||
msg.add(ChatColor.RED+"封禁者会在此处公示: https://starpavilion.xyz/bans.html");
|
msg.add(ChatColor.RED+"封禁者会在此处公示: https://starpavilion.xyz/bans.html");
|
||||||
msg.add(ChatColor.RED + "信用分过低将会导致你的账号受限甚至永久封禁!");
|
msg.add(ChatColor.RED + "信用分过低将会导致你的账号受限甚至永久封禁!");
|
||||||
|
msg.add(ChatColor.GREEN+"小小赞助一下吧~");
|
||||||
msg.add(ChatColor.GREEN + "信用分被扣除会提醒,如果您的信用分被莫名扣除,请及时到QQ群 717903781 申诉。");
|
msg.add(ChatColor.GREEN + "信用分被扣除会提醒,如果您的信用分被莫名扣除,请及时到QQ群 717903781 申诉。");
|
||||||
|
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@@ -220,14 +222,21 @@ public final class Survival extends JavaPlugin {
|
|||||||
// 根据剩余信用分执行处罚
|
// 根据剩余信用分执行处罚
|
||||||
if (newPoint > 80) {
|
if (newPoint > 80) {
|
||||||
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "无");
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "无");
|
||||||
} else if (newPoint <= 0) {
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(0);
|
||||||
|
} else if(newPoint <= 0){
|
||||||
plr.kickPlayer(ChatColor.RED + "您好! 您由于在 " + formattedTime + " 信用分小于 0 ,我们决定对你的账号 " + plr.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
plr.kickPlayer(ChatColor.RED + "您好! 您由于在 " + formattedTime + " 信用分小于 0 ,我们决定对你的账号 " + plr.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
||||||
} else if (newPoint <= 20) {
|
}else if (newPoint <= 10) {
|
||||||
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用");
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用");
|
||||||
} else if (newPoint <= 50) {
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(4);
|
||||||
|
} else if (newPoint <= 30) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(3);
|
||||||
|
} else if (newPoint <= 60) {
|
||||||
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,限制功能使用");
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,限制功能使用");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(2);
|
||||||
} else if (newPoint <= 80) {
|
} else if (newPoint <= 80) {
|
||||||
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "监管行为及发言,签到将奖励转换为信用分");
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "监管行为及发言,签到将奖励转换为信用分");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(1);
|
||||||
}
|
}
|
||||||
plr.sendMessage(ChatColor.RED + "|为了维护游戏环境,请不要违规!");
|
plr.sendMessage(ChatColor.RED + "|为了维护游戏环境,请不要违规!");
|
||||||
plr.sendMessage(ChatColor.RED + "----------------------------");
|
plr.sendMessage(ChatColor.RED + "----------------------------");
|
||||||
@@ -253,6 +262,7 @@ public final class Survival extends JavaPlugin {
|
|||||||
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
|
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
|
||||||
Bukkit.getPluginManager().registerEvents(new LoginEvent(this), this);
|
Bukkit.getPluginManager().registerEvents(new LoginEvent(this), this);
|
||||||
Bukkit.getPluginManager().registerEvents(new LandEvent(this),this);
|
Bukkit.getPluginManager().registerEvents(new LandEvent(this),this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(new ForceSurvival(),this);
|
||||||
|
|
||||||
// 注册命令执行器
|
// 注册命令执行器
|
||||||
getCommand("report").setExecutor(new ReportCommandExecutor(this));
|
getCommand("report").setExecutor(new ReportCommandExecutor(this));
|
||||||
@@ -274,7 +284,7 @@ public final class Survival extends JavaPlugin {
|
|||||||
getCommand("msg").setExecutor(new msgCommandExecutor(this));
|
getCommand("msg").setExecutor(new msgCommandExecutor(this));
|
||||||
getCommand("friend").setExecutor(new FriendCommandExecutor(this));
|
getCommand("friend").setExecutor(new FriendCommandExecutor(this));
|
||||||
getCommand("point").setExecutor(new PointCommandExecutor(this));
|
getCommand("point").setExecutor(new PointCommandExecutor(this));
|
||||||
getCommand("selfkill").setExecutor(new KillCommandExecutor(this));
|
getCommand("home").setExecutor(new KillCommandExecutor(this));
|
||||||
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));
|
||||||
@@ -285,7 +295,7 @@ public final class Survival extends JavaPlugin {
|
|||||||
// 所有需要补全的命令列表
|
// 所有需要补全的命令列表
|
||||||
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", "selfkill",
|
"point", "land", "pvp", "help", "tag", "hub", "home",
|
||||||
"qd", "reg", "login", "msg", "inventory", "notice"
|
"qd", "reg", "login", "msg", "inventory", "notice"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -309,7 +319,6 @@ public final class Survival extends JavaPlugin {
|
|||||||
try {
|
try {
|
||||||
FriendCommandExecutor executor = (FriendCommandExecutor) this.getCommand("friend").getExecutor();
|
FriendCommandExecutor executor = (FriendCommandExecutor) this.getCommand("friend").getExecutor();
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
executor.saveFriendData();
|
|
||||||
getLogger().info("好友数据已保存!");
|
getLogger().info("好友数据已保存!");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class CommandTabExecutor implements TabCompleter {
|
|||||||
case "help":
|
case "help":
|
||||||
case "tag":
|
case "tag":
|
||||||
case "hub":
|
case "hub":
|
||||||
case "selfkill":
|
case "home":
|
||||||
case "qd":
|
case "qd":
|
||||||
case "reg":
|
case "reg":
|
||||||
case "login":
|
case "login":
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.xgqy.survival.command;
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import com.google.gson.Gson;
|
||||||
import net.md_5.bungee.api.chat.ClickEvent;
|
import com.google.gson.GsonBuilder;
|
||||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
@@ -11,476 +10,577 @@ import org.bukkit.command.CommandSender;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.xgqy.survival.FriendData;
|
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileReader;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.lang.reflect.Type;
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FriendCommandExecutor implements CommandExecutor {
|
public class FriendCommandExecutor implements CommandExecutor {
|
||||||
|
// 命令前缀
|
||||||
|
private final String prefix = ChatColor.LIGHT_PURPLE + "好友系统 " + ChatColor.WHITE + "| ";
|
||||||
|
// 好友关系存储(双向绑定)
|
||||||
|
private Map<Player, List<Player>> friends;
|
||||||
|
// 待处理好友请求(key:接收者,value:请求列表)
|
||||||
|
private Map<Player, List<FriendRequest>> waitingRequests;
|
||||||
|
// Gson实例(JSON序列化)
|
||||||
|
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
// 数据文件
|
||||||
|
private final File dataFile;
|
||||||
|
// 插件实例
|
||||||
private final Survival plugin;
|
private final Survival plugin;
|
||||||
// 好友请求超时任务缓存(使用字符串键确保精准匹配)
|
// 请求有效期(5分钟 = 300000毫秒)
|
||||||
private final Map<String, BukkitRunnable> timeoutTasks = new ConcurrentHashMap<>();
|
private static final long REQUEST_EXPIRE_TIME = 5 * 60 * 1000;
|
||||||
// 待确认的删除请求
|
|
||||||
private final Map<Player, Player> pendingDelete = new ConcurrentHashMap<>();
|
|
||||||
// 删除确认超时任务缓存
|
|
||||||
private final Map<Player, BukkitRunnable> deleteTimeoutTasks = new ConcurrentHashMap<>();
|
|
||||||
// 持久化文件
|
|
||||||
private final File friendDataFile;
|
|
||||||
|
|
||||||
public FriendCommandExecutor(Survival plugin) {
|
public FriendCommandExecutor(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.friendDataFile = new File(plugin.getDataFolder(), "friend_data.ser");
|
// 初始化数据文件
|
||||||
loadFriendData(); // 加载数据
|
this.dataFile = new File(plugin.getDataFolder(), "friend.json");
|
||||||
|
// 确保数据文件夹存在
|
||||||
|
if (!plugin.getDataFolder().exists()) {
|
||||||
|
plugin.getDataFolder().mkdirs();
|
||||||
|
}
|
||||||
|
// 加载历史数据
|
||||||
|
loadData();
|
||||||
|
// 启动请求过期清理任务
|
||||||
|
startExpireCheckTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提供外部访问任务的方法(用于插件禁用时清理)
|
/**
|
||||||
public Map<String, BukkitRunnable> getTimeoutTasks() {
|
* 好友请求实体(存储发送者UUID和时间戳,支持序列化)
|
||||||
return timeoutTasks;
|
*/
|
||||||
|
private static class FriendRequest {
|
||||||
|
private final String senderUuid;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
public FriendRequest(Player sender) {
|
||||||
|
this.senderUuid = sender.getUniqueId().toString();
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Player, BukkitRunnable> getDeleteTimeoutTasks() {
|
// Getter
|
||||||
return deleteTimeoutTasks;
|
public String getSenderUuid() {
|
||||||
|
return senderUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON序列化辅助类
|
||||||
|
*/
|
||||||
|
private static class FriendData {
|
||||||
|
private Map<String, List<String>> friends;
|
||||||
|
private Map<String, List<FriendRequest>> waitingRequests;
|
||||||
|
|
||||||
|
// 必须保留默认构造函数(Gson反序列化用)
|
||||||
|
public FriendData() {}
|
||||||
|
|
||||||
|
public FriendData(Map<String, List<String>> friends, Map<String, List<FriendRequest>> waitingRequests) {
|
||||||
|
this.friends = friends;
|
||||||
|
this.waitingRequests = waitingRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter & Setter
|
||||||
|
public Map<String, List<String>> getFriends() {
|
||||||
|
return friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFriends(Map<String, List<String>> friends) {
|
||||||
|
this.friends = friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<FriendRequest>> getWaitingRequests() {
|
||||||
|
return waitingRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitingRequests(Map<String, List<FriendRequest>> waitingRequests) {
|
||||||
|
this.waitingRequests = waitingRequests;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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)) {
|
// 仅玩家可使用
|
||||||
sender.sendMessage(ChatColor.RED + "❌ 无法对非玩家类使用该命令!");
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "仅玩家能使用该命令!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理隐式命令(点击按钮触发)
|
Player player = (Player) sender;
|
||||||
if (args.length >= 3 && args[0].equals("confirmDelete")) {
|
|
||||||
handleConfirmDelete(player, args[1], args[2]);
|
// 无参数时显示帮助
|
||||||
return true;
|
if (args.length == 0) {
|
||||||
}
|
showHelp(player);
|
||||||
if (args.length >= 2 && args[0].equals("prepareDelete")) {
|
|
||||||
handlePrepareDeleteCommand(player, args);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基础参数校验
|
// 处理子命令
|
||||||
if (args.length < 1) {
|
switch (args[0].toLowerCase()) {
|
||||||
sendUsage(player);
|
case "add":
|
||||||
return true;
|
handleAdd(player, args);
|
||||||
}
|
break;
|
||||||
|
case "remove":
|
||||||
String subCommand = args[0].toLowerCase();
|
handleRemove(player, args);
|
||||||
if (subCommand.equals("list")) {
|
break;
|
||||||
|
case "list":
|
||||||
handleList(player);
|
handleList(player);
|
||||||
|
break;
|
||||||
|
case "accept":
|
||||||
|
handleAccept(player, args);
|
||||||
|
break;
|
||||||
|
case "deny":
|
||||||
|
handleDeny(player, args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
player.sendMessage(prefix + ChatColor.RED + "未知命令!输入 /friend 查看帮助");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length < 2) {
|
/**
|
||||||
sendUsage(player);
|
* 处理添加好友命令
|
||||||
return true;
|
*/
|
||||||
|
private void handleAdd(Player sender, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend add <玩家名>");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String targetName = args[1];
|
String targetName = args[1];
|
||||||
Player target = Bukkit.getPlayerExact(targetName); // 精准匹配玩家名(修复)
|
Player target = plugin.getServer().getPlayer(targetName);
|
||||||
|
|
||||||
// 目标玩家有效性校验
|
// 目标玩家校验
|
||||||
if (!subCommand.equals("remove") && (target == null || !target.isOnline())) {
|
if (target == null || !target.isOnline()) {
|
||||||
player.sendMessage(ChatColor.RED + "❌ 目标玩家不存在或未在线!");
|
sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!");
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
if (target.equals(sender)) {
|
||||||
// 分发子命令
|
sender.sendMessage(prefix + ChatColor.RED + "不能添加自己为好友!");
|
||||||
switch (subCommand) {
|
|
||||||
case "add" -> handleAdd(player, target);
|
|
||||||
case "remove" -> handleRemove(player, targetName);
|
|
||||||
case "allow" -> handleAllow(player, target);
|
|
||||||
case "deny" -> handleDeny(player, target);
|
|
||||||
default -> sendUsage(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载历史好友数据
|
|
||||||
*/
|
|
||||||
public void loadFriendData() {
|
|
||||||
if (!friendDataFile.exists()) {
|
|
||||||
plugin.getLogger().info("好友数据文件不存在,初始化空数据!");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(friendDataFile))) {
|
// 检查是否已是好友
|
||||||
FriendData data = (FriendData) ois.readObject();
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
Map<UUID, List<UUID>> uuidFriends = data.getFriends();
|
if (senderFriends.contains(target)) {
|
||||||
Map<UUID, List<UUID>> uuidWaiting = data.getWaiting();
|
sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 已是好友关系!");
|
||||||
|
|
||||||
// 转换好友数据:UUID -> Player(在线玩家)
|
|
||||||
for (Map.Entry<UUID, List<UUID>> entry : uuidFriends.entrySet()) {
|
|
||||||
Player player = Bukkit.getPlayer(entry.getKey());
|
|
||||||
if (player != null && player.isOnline()) {
|
|
||||||
List<Player> friends = entry.getValue().stream()
|
|
||||||
.map(Bukkit::getPlayer)
|
|
||||||
.filter(p -> p != null && p.isOnline()) // 只保留在线玩家
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
plugin.friends.put(player, friends);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换等待请求数据:UUID -> Player(在线玩家)
|
|
||||||
for (Map.Entry<UUID, List<UUID>> entry : uuidWaiting.entrySet()) {
|
|
||||||
Player receiver = Bukkit.getPlayer(entry.getKey());
|
|
||||||
if (receiver != null && receiver.isOnline()) {
|
|
||||||
List<Player> requesters = entry.getValue().stream()
|
|
||||||
.map(Bukkit::getPlayer)
|
|
||||||
.filter(p -> p != null && p.isOnline()) // 只保留在线玩家
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
plugin.waiting.put(receiver, requesters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.getLogger().info("好友数据加载成功!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().severe("好友数据文件读取失败:" + e.getMessage());
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
plugin.getLogger().severe("好友数据模型类未找到:" + e.getMessage());
|
|
||||||
} catch (Exception e) {
|
|
||||||
plugin.getLogger().severe("好友数据加载异常:" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存好友数据(使用副本遍历避免并发异常)
|
|
||||||
*/
|
|
||||||
public void saveFriendData() {
|
|
||||||
FriendData data = new FriendData();
|
|
||||||
Map<UUID, List<UUID>> uuidFriends = new ConcurrentHashMap<>();
|
|
||||||
Map<UUID, List<UUID>> uuidWaiting = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
// 遍历副本避免ConcurrentModificationException(关键修复)
|
|
||||||
new ArrayList<>(plugin.friends.entrySet()).forEach(entry -> {
|
|
||||||
UUID playerUuid = entry.getKey().getUniqueId();
|
|
||||||
List<UUID> friendUuids = entry.getValue().stream()
|
|
||||||
.map(Player::getUniqueId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
uuidFriends.put(playerUuid, friendUuids);
|
|
||||||
});
|
|
||||||
|
|
||||||
new ArrayList<>(plugin.waiting.entrySet()).forEach(entry -> {
|
|
||||||
UUID receiverUuid = entry.getKey().getUniqueId();
|
|
||||||
List<UUID> requesterUuids = entry.getValue().stream()
|
|
||||||
.map(Player::getUniqueId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
uuidWaiting.put(receiverUuid, requesterUuids);
|
|
||||||
});
|
|
||||||
|
|
||||||
data.setFriends(uuidFriends);
|
|
||||||
data.setWaiting(uuidWaiting);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!friendDataFile.getParentFile().exists()) {
|
|
||||||
friendDataFile.getParentFile().mkdirs();
|
|
||||||
}
|
|
||||||
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(friendDataFile))) {
|
|
||||||
oos.writeObject(data);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().severe("好友数据保存失败:" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理好友列表命令
|
|
||||||
*/
|
|
||||||
private void handleList(Player player) {
|
|
||||||
plugin.friends.computeIfAbsent(player, k -> new ArrayList<>());
|
|
||||||
List<Player> friends = plugin.friends.get(player);
|
|
||||||
|
|
||||||
if (friends.isEmpty()) {
|
|
||||||
player.sendMessage(ChatColor.RED + "📭 你的好友列表为空!使用 /friend add <玩家名> 添加好友");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.sendMessage(ChatColor.GREEN + "===== 你的好友列表(共" + friends.size() + "人)=====");
|
// 检查是否有未过期的请求
|
||||||
for (Player friend : friends) {
|
List<FriendRequest> targetRequests = waitingRequests.getOrDefault(target, new ArrayList<>());
|
||||||
BaseComponent[] friendEntry = new ComponentBuilder()
|
boolean hasPending = targetRequests.stream()
|
||||||
.append(ChatColor.WHITE + "● " + friend.getName())
|
.anyMatch(req -> req.getSenderUuid().equals(sender.getUniqueId().toString()) &&
|
||||||
.append(ChatColor.RED + " [删除]")
|
System.currentTimeMillis() - req.getTimestamp() < REQUEST_EXPIRE_TIME);
|
||||||
.event(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND,
|
|
||||||
"/friend prepareDelete " + friend.getName()
|
|
||||||
))
|
|
||||||
.create();
|
|
||||||
player.spigot().sendMessage(friendEntry);
|
|
||||||
}
|
|
||||||
player.sendMessage(ChatColor.GREEN + "======================================");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (hasPending) {
|
||||||
* 准备删除好友(触发二次确认)
|
sender.sendMessage(prefix + ChatColor.RED + "已向 " + target.getName() + " 发送请求,对方未处理(5分钟内不可重复发送)!");
|
||||||
*/
|
|
||||||
private void handlePrepareDelete(Player player, String targetName) {
|
|
||||||
Player target = Bukkit.getPlayerExact(targetName);
|
|
||||||
plugin.friends.computeIfAbsent(player, k -> new ArrayList<>());
|
|
||||||
|
|
||||||
if (target == null || !plugin.friends.get(player).contains(target)) {
|
|
||||||
player.sendMessage(ChatColor.RED + "❌ 该玩家不在你的好友列表中!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingDelete.containsKey(player)) {
|
|
||||||
player.sendMessage(ChatColor.RED + "❌ 你有未完成的删除确认请求,请先处理!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingDelete.put(player, target);
|
|
||||||
player.sendMessage(ChatColor.YELLOW + "⚠️ 确认要删除好友 " + target.getName() + " 吗?(10秒内有效)");
|
|
||||||
|
|
||||||
BaseComponent[] confirmButtons = new ComponentBuilder()
|
|
||||||
.append(ChatColor.GREEN + "[确认删除] ")
|
|
||||||
.event(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND,
|
|
||||||
"/friend confirmDelete " + target.getName() + " yes"
|
|
||||||
))
|
|
||||||
.append(ChatColor.GRAY + "[取消]")
|
|
||||||
.event(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND,
|
|
||||||
"/friend confirmDelete " + target.getName() + " no"
|
|
||||||
))
|
|
||||||
.create();
|
|
||||||
player.spigot().sendMessage(confirmButtons);
|
|
||||||
|
|
||||||
// 设置超时任务
|
|
||||||
BukkitRunnable timeoutTask = new BukkitRunnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (pendingDelete.containsKey(player) && pendingDelete.get(player).equals(target)) {
|
|
||||||
pendingDelete.remove(player);
|
|
||||||
player.sendMessage(ChatColor.RED + "⌛ 删除好友请求已超时,自动取消!");
|
|
||||||
}
|
|
||||||
deleteTimeoutTasks.remove(player);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
deleteTimeoutTasks.put(player, timeoutTask);
|
|
||||||
timeoutTask.runTaskLater(plugin, 20 * 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理删除确认/取消操作
|
|
||||||
*/
|
|
||||||
private void handleConfirmDelete(Player player, String targetName, String action) {
|
|
||||||
Player target = Bukkit.getPlayerExact(targetName);
|
|
||||||
|
|
||||||
if (!pendingDelete.containsKey(player) || !pendingDelete.get(player).equals(target)) {
|
|
||||||
player.sendMessage(ChatColor.RED + "❌ 无对应的删除确认请求!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleteTimeoutTasks.containsKey(player)) {
|
|
||||||
deleteTimeoutTasks.get(player).cancel();
|
|
||||||
deleteTimeoutTasks.remove(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.equalsIgnoreCase("yes")) {
|
|
||||||
handleRemove(player, targetName);
|
|
||||||
} else if (action.equalsIgnoreCase("no")) {
|
|
||||||
player.sendMessage(ChatColor.GREEN + "✅ 已取消删除好友 " + target.getName() + "!");
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingDelete.remove(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理添加好友命令(添加异常捕获)
|
|
||||||
*/
|
|
||||||
private void handleAdd(Player requester, Player target) {
|
|
||||||
try {
|
|
||||||
if (requester.equals(target)) {
|
|
||||||
requester.sendMessage(ChatColor.RED + "❌ 无法添加自己为好友!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>());
|
|
||||||
plugin.friends.computeIfAbsent(target, k -> new ArrayList<>());
|
|
||||||
plugin.waiting.computeIfAbsent(target, k -> new ArrayList<>());
|
|
||||||
|
|
||||||
if (plugin.friends.get(requester).contains(target)) {
|
|
||||||
requester.sendMessage(ChatColor.RED + "❌ " + target.getName() + " 已是你的好友!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.waiting.get(target).contains(requester)) {
|
|
||||||
requester.sendMessage(ChatColor.RED + "⌛ 你已向 " + target.getName() + " 发送过好友请求,等待对方确认中!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.waiting.get(requester).contains(target)) {
|
|
||||||
requester.sendMessage(ChatColor.YELLOW + "📩 " + target.getName() + " 已向你发送好友请求,请使用 /friend allow " + target.getName() + " 同意!");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
plugin.waiting.get(target).add(requester);
|
targetRequests.add(new FriendRequest(sender));
|
||||||
requester.sendMessage(ChatColor.GREEN + "✅ 好友请求已发送给 " + target.getName() + ",对方需在5分钟内确认!");
|
waitingRequests.put(target, targetRequests);
|
||||||
target.sendMessage(ChatColor.AQUA + "📩 收到 " + requester.getName() + " 的好友请求!");
|
|
||||||
target.sendMessage(ChatColor.AQUA + "使用 /friend allow " + requester.getName() + " 同意,或 /friend deny " + requester.getName() + " 拒绝");
|
|
||||||
|
|
||||||
// 生成按钮
|
// 通知双方
|
||||||
BaseComponent[] message = new ComponentBuilder()
|
sender.sendMessage(prefix + ChatColor.GREEN + "好友请求已发送给 " + target.getName() + ",请等待回应~");
|
||||||
.append(new ComponentBuilder(ChatColor.GREEN + "[同意] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend allow " + requester.getName())).create())
|
target.sendMessage(prefix + ChatColor.YELLOW + "📩 收到 " + sender.getName() + " 的好友请求!");
|
||||||
.append(new ComponentBuilder(ChatColor.RED + "[拒绝]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend deny " + requester.getName())).create())
|
target.sendMessage(prefix + ChatColor.YELLOW + "使用 /friend accept " + sender.getName() + " 接受,或 /friend deny " + sender.getName() + " 拒绝");
|
||||||
.create();
|
|
||||||
target.spigot().sendMessage(message);
|
|
||||||
|
|
||||||
// 超时任务(使用唯一键精准管理)
|
// 保存数据
|
||||||
String taskKey = requester.getUniqueId() + "_" + target.getUniqueId();
|
saveData();
|
||||||
BukkitRunnable timeoutTask = new BukkitRunnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (plugin.waiting.getOrDefault(target, new ArrayList<>()).contains(requester)) {
|
|
||||||
plugin.waiting.get(target).remove(requester);
|
|
||||||
requester.sendMessage(ChatColor.RED + "⌛ 向 " + target.getName() + " 发送的好友请求已超时(5分钟)!");
|
|
||||||
target.sendMessage(ChatColor.RED + "⌛ " + requester.getName() + " 的好友请求已超时,已自动拒绝!");
|
|
||||||
saveFriendData();
|
|
||||||
}
|
}
|
||||||
timeoutTasks.remove(taskKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
timeoutTasks.put(taskKey, timeoutTask);
|
|
||||||
timeoutTask.runTaskLater(plugin, 20 * 60 * 5);
|
|
||||||
|
|
||||||
saveFriendData();
|
/**
|
||||||
} catch (Exception e) {
|
* 处理移除好友命令
|
||||||
requester.sendMessage(ChatColor.RED + "❌ 添加好友失败!请联系管理员。");
|
*/
|
||||||
plugin.getLogger().severe("添加好友异常(" + requester.getName() + " -> " + target.getName() + "):" + e.getMessage());
|
private void handleRemove(Player sender, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend remove <玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = args[1];
|
||||||
|
Player target = plugin.getServer().getPlayer(targetName);
|
||||||
|
|
||||||
|
// 目标玩家校验
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target.equals(sender)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "不能移除自己!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查好友关系
|
||||||
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
|
List<Player> targetFriends = friends.getOrDefault(target, new ArrayList<>());
|
||||||
|
|
||||||
|
if (!senderFriends.contains(target) || !targetFriends.contains(sender)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 不是好友关系!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双向移除好友
|
||||||
|
senderFriends.remove(target);
|
||||||
|
targetFriends.remove(sender);
|
||||||
|
friends.put(sender, senderFriends);
|
||||||
|
friends.put(target, targetFriends);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
sender.sendMessage(prefix + ChatColor.GRAY + "已成功移除好友 " + target.getName() + "!");
|
||||||
|
target.sendMessage(prefix + ChatColor.RED + "⚠️ 你被 " + sender.getName() + " 移除好友!");
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查看好友列表命令
|
||||||
|
*/
|
||||||
|
private void handleList(Player player) {
|
||||||
|
List<Player> friendList = friends.getOrDefault(player, new ArrayList<>());
|
||||||
|
|
||||||
|
// 空列表提示
|
||||||
|
if (friendList.isEmpty()) {
|
||||||
|
player.sendMessage(prefix + ChatColor.GRAY + "你的好友列表为空~ 使用 /friend add <玩家名> 添加好友吧!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示好友列表(带在线状态)
|
||||||
|
player.sendMessage(prefix + ChatColor.GREEN + "=== 好友列表(共 " + friendList.size() + " 人)===");
|
||||||
|
for (int i = 0; i < friendList.size(); i++) {
|
||||||
|
Player friend = friendList.get(i);
|
||||||
|
String status = friend.isOnline() ? ChatColor.GREEN + "在线 " : ChatColor.RED + " 离线";
|
||||||
|
player.sendMessage(ChatColor.WHITE + String.format("%d",(i + 1)) + ". " + friend.getName() + " - " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理接受好友请求命令
|
||||||
|
*/
|
||||||
|
private void handleAccept(Player receiver, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend accept <请求ID|玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找目标请求(支持ID和玩家名)
|
||||||
|
FriendRequest targetRequest = findRequest(receiver, args[1]);
|
||||||
|
if (targetRequest == null) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查请求是否过期
|
||||||
|
if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid()));
|
||||||
|
if (sender == null || !sender.isOnline()) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "请求发送者已离线,请求失效!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双向添加好友
|
||||||
|
List<Player> receiverFriends = friends.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
|
|
||||||
|
receiverFriends.add(sender);
|
||||||
|
senderFriends.add(receiver);
|
||||||
|
friends.put(receiver, receiverFriends);
|
||||||
|
friends.put(sender, senderFriends);
|
||||||
|
|
||||||
|
// 移除请求
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
receiver.sendMessage(prefix + ChatColor.GREEN + "🎉 已接受 " + sender.getName() + " 的好友请求!现在可以一起玩啦~");
|
||||||
|
sender.sendMessage(prefix + ChatColor.GREEN + "🎉 " + receiver.getName() + " 接受了你的好友请求!你们成为好友啦~");
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理拒绝好友请求命令
|
||||||
|
*/
|
||||||
|
private void handleDeny(Player receiver, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend deny <请求ID|玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找目标请求
|
||||||
|
FriendRequest targetRequest = findRequest(receiver, args[1]);
|
||||||
|
if (targetRequest == null) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查请求是否过期
|
||||||
|
if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid()));
|
||||||
|
|
||||||
|
// 移除请求
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
receiver.sendMessage(prefix + ChatColor.GRAY + "已拒绝该好友请求!");
|
||||||
|
if (sender != null && sender.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "❌ " + receiver.getName() + " 拒绝了你的好友请求!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找好友请求(支持ID和玩家名)
|
||||||
|
*/
|
||||||
|
private FriendRequest findRequest(Player receiver, String param) {
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
|
||||||
|
// 尝试按ID查找(数字)
|
||||||
|
try {
|
||||||
|
int requestId = Integer.parseInt(param) - 1; // 转为0-based索引
|
||||||
|
if (requestId >= 0 && requestId < requests.size()) {
|
||||||
|
return requests.get(requestId);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 按玩家名查找(忽略大小写)
|
||||||
|
return requests.stream()
|
||||||
|
.filter(req -> {
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid()));
|
||||||
|
return sender != null && sender.getName().equalsIgnoreCase(param);
|
||||||
|
})
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除好友请求
|
||||||
|
*/
|
||||||
|
private void removeRequest(Player receiver, FriendRequest request) {
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
requests.remove(request);
|
||||||
|
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
waitingRequests.remove(receiver);
|
||||||
|
} else {
|
||||||
|
waitingRequests.put(receiver, requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示命令帮助
|
||||||
|
*/
|
||||||
|
private void showHelp(Player player) {
|
||||||
|
player.sendMessage(prefix + ChatColor.GREEN + "=== 好友系统命令帮助 ===");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend add <玩家名> " + ChatColor.GRAY + "- 向玩家发送好友请求");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend remove <玩家名> " + ChatColor.GRAY + "- 移除指定好友");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend list " + ChatColor.GRAY + "- 查看好友列表及在线状态");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend accept <ID|玩家名> " + ChatColor.GRAY + "- 接受好友请求");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend deny <ID|玩家名> " + ChatColor.GRAY + "- 拒绝好友请求");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "小贴士:好友请求有效期为5分钟,超时自动失效~");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载好友数据(从JSON文件)
|
||||||
|
*/
|
||||||
|
private void loadData() {
|
||||||
|
if (!dataFile.exists()) {
|
||||||
|
// 初始化空数据
|
||||||
|
friends = new ConcurrentHashMap<>();
|
||||||
|
waitingRequests = new ConcurrentHashMap<>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(dataFile)) {
|
||||||
|
Type dataType = new TypeToken<FriendData>() {}.getType();
|
||||||
|
FriendData data = gson.fromJson(reader, dataType);
|
||||||
|
|
||||||
|
// 转换UUID映射为Player映射(仅加载在线玩家)
|
||||||
|
this.friends = convertUuidToPlayerMap(data.getFriends());
|
||||||
|
this.waitingRequests = convertUuidToRequestMap(data.getWaitingRequests());
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载好友数据失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
// 初始化空数据兜底
|
||||||
|
friends = new ConcurrentHashMap<>();
|
||||||
|
waitingRequests = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存好友数据(到JSON文件)
|
||||||
|
*/
|
||||||
|
private void saveData() {
|
||||||
|
// 转换Player映射为UUID映射(支持序列化)
|
||||||
|
Map<String, List<String>> serializableFriends = convertPlayerToUuidMap(friends);
|
||||||
|
Map<String, List<FriendRequest>> serializableRequests = convertRequestToUuidMap(waitingRequests);
|
||||||
|
|
||||||
|
FriendData data = new FriendData(serializableFriends, serializableRequests);
|
||||||
|
|
||||||
|
try (FileWriter writer = new FileWriter(dataFile)) {
|
||||||
|
gson.toJson(data, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存好友数据失败!" + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理删除好友命令
|
* 转换:Map<String(UUID), List<String(UUID)>> → Map<Player, List<Player>>
|
||||||
*/
|
*/
|
||||||
private void handleRemove(Player player, String targetName) {
|
private Map<Player, List<Player>> convertUuidToPlayerMap(Map<String, List<String>> uuidMap) {
|
||||||
Player target = Bukkit.getPlayerExact(targetName);
|
Map<Player, List<Player>> playerMap = new ConcurrentHashMap<>();
|
||||||
plugin.friends.computeIfAbsent(player, k -> new ArrayList<>());
|
if (uuidMap == null) return playerMap;
|
||||||
|
|
||||||
if (target == null) {
|
for (Map.Entry<String, List<String>> entry : uuidMap.entrySet()) {
|
||||||
boolean removed = false;
|
// 查找在线的玩家(接收者)
|
||||||
List<Player> friends = plugin.friends.get(player);
|
Player owner = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
|
||||||
for (Player friend : new ArrayList<>(friends)) { // 遍历副本
|
if (owner == null) continue;
|
||||||
if (friend.getName().equals(targetName)) {
|
|
||||||
friends.remove(friend);
|
|
||||||
plugin.friends.computeIfAbsent(friend, k -> new ArrayList<>()).remove(player);
|
|
||||||
removed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (removed) {
|
|
||||||
player.sendMessage(ChatColor.GREEN + "✅ 已成功删除好友 " + targetName + "!");
|
|
||||||
saveFriendData();
|
|
||||||
} else {
|
|
||||||
player.sendMessage(ChatColor.RED + "❌ 你未添加 " + targetName + " 为好友!");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.friends.get(player).contains(target)) {
|
// 转换好友列表(仅保留在线玩家)
|
||||||
plugin.friends.get(player).remove(target);
|
List<Player> friends = entry.getValue().stream()
|
||||||
plugin.friends.computeIfAbsent(target, k -> new ArrayList<>()).remove(player);
|
.map(uuidStr -> plugin.getServer().getPlayer(UUID.fromString(uuidStr)))
|
||||||
player.sendMessage(ChatColor.GREEN + "✅ 已成功删除好友 " + target.getName() + "!");
|
.filter(Objects::nonNull)
|
||||||
target.sendMessage(ChatColor.RED + "📢 " + player.getName() + " 已将你从好友列表中移除!");
|
.collect(Collectors.toList());
|
||||||
saveFriendData();
|
|
||||||
} else {
|
playerMap.put(owner, friends);
|
||||||
player.sendMessage(ChatColor.RED + "❌ 你未添加 " + target.getName() + " 为好友!");
|
|
||||||
}
|
}
|
||||||
|
return playerMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理同意好友请求命令(精准取消超时任务)
|
* 转换:Map<Player, List<Player>> → Map<String(UUID), List<String(UUID)>>
|
||||||
*/
|
*/
|
||||||
private void handleAllow(Player receiver, Player requester) {
|
private Map<String, List<String>> convertPlayerToUuidMap(Map<Player, List<Player>> playerMap) {
|
||||||
plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>());
|
Map<String, List<String>> uuidMap = new ConcurrentHashMap<>();
|
||||||
plugin.friends.computeIfAbsent(receiver, k -> new ArrayList<>());
|
|
||||||
plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>());
|
|
||||||
|
|
||||||
if (!plugin.waiting.get(receiver).contains(requester)) {
|
for (Map.Entry<Player, List<Player>> entry : playerMap.entrySet()) {
|
||||||
receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!");
|
String ownerUuid = entry.getKey().getUniqueId().toString();
|
||||||
return;
|
List<String> friendUuids = entry.getValue().stream()
|
||||||
|
.map(player -> player.getUniqueId().toString())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
uuidMap.put(ownerUuid, friendUuids);
|
||||||
}
|
}
|
||||||
|
return uuidMap;
|
||||||
// 双向添加好友
|
|
||||||
plugin.friends.get(receiver).add(requester);
|
|
||||||
plugin.friends.get(requester).add(receiver);
|
|
||||||
plugin.waiting.get(receiver).remove(requester);
|
|
||||||
|
|
||||||
// 精准取消超时任务(关键修复)
|
|
||||||
String taskKey = requester.getUniqueId() + "_" + receiver.getUniqueId();
|
|
||||||
BukkitRunnable task = timeoutTasks.get(taskKey);
|
|
||||||
if (task != null && !task.isCancelled()) {
|
|
||||||
task.cancel();
|
|
||||||
timeoutTasks.remove(taskKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
receiver.sendMessage(ChatColor.GREEN + "🎉 已同意 " + requester.getName() + " 的好友请求,你们现在是好友啦!");
|
|
||||||
requester.sendMessage(ChatColor.GREEN + "🎉 " + receiver.getName() + " 已同意你的好友请求,你们现在是好友啦!");
|
|
||||||
saveFriendData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理拒绝好友请求命令(精准取消超时任务)
|
* 转换:Map<String(UUID), List<FriendRequest>> → Map<Player, List<FriendRequest>>
|
||||||
*/
|
*/
|
||||||
private void handleDeny(Player receiver, Player requester) {
|
private Map<Player, List<FriendRequest>> convertUuidToRequestMap(Map<String, List<FriendRequest>> uuidMap) {
|
||||||
plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>());
|
Map<Player, List<FriendRequest>> playerMap = new ConcurrentHashMap<>();
|
||||||
|
if (uuidMap == null) return playerMap;
|
||||||
|
|
||||||
if (!plugin.waiting.get(receiver).contains(requester)) {
|
for (Map.Entry<String, List<FriendRequest>> entry : uuidMap.entrySet()) {
|
||||||
receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!");
|
Player receiver = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
|
||||||
return;
|
if (receiver != null) {
|
||||||
|
playerMap.put(receiver, entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.waiting.get(receiver).remove(requester);
|
|
||||||
|
|
||||||
// 精准取消超时任务(关键修复)
|
|
||||||
String taskKey = requester.getUniqueId() + "_" + receiver.getUniqueId();
|
|
||||||
BukkitRunnable task = timeoutTasks.get(taskKey);
|
|
||||||
if (task != null && !task.isCancelled()) {
|
|
||||||
task.cancel();
|
|
||||||
timeoutTasks.remove(taskKey);
|
|
||||||
}
|
}
|
||||||
|
return playerMap;
|
||||||
receiver.sendMessage(ChatColor.GRAY + "✅ 已拒绝 " + requester.getName() + " 的好友请求!");
|
|
||||||
requester.sendMessage(ChatColor.RED + "❌ " + receiver.getName() + " 已拒绝你的好友请求!");
|
|
||||||
saveFriendData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送命令用法提示
|
* 转换:Map<Player, List<FriendRequest>> → Map<String(UUID), List<FriendRequest>>
|
||||||
*/
|
*/
|
||||||
private void sendUsage(Player player) {
|
private Map<String, List<FriendRequest>> convertRequestToUuidMap(Map<Player, List<FriendRequest>> playerMap) {
|
||||||
player.sendMessage(ChatColor.GREEN + "===== 好友系统命令指南 =====");
|
Map<String, List<FriendRequest>> uuidMap = new ConcurrentHashMap<>();
|
||||||
player.sendMessage(ChatColor.YELLOW + "/friend add <玩家名> - 向指定玩家发送好友请求");
|
|
||||||
player.sendMessage(ChatColor.YELLOW + "/friend remove <玩家名> - 从好友列表中删除指定玩家");
|
for (Map.Entry<Player, List<FriendRequest>> entry : playerMap.entrySet()) {
|
||||||
player.sendMessage(ChatColor.YELLOW + "/friend allow <玩家名> - 同意指定玩家的好友请求");
|
String receiverUuid = entry.getKey().getUniqueId().toString();
|
||||||
player.sendMessage(ChatColor.YELLOW + "/friend deny <玩家名> - 拒绝指定玩家的好友请求");
|
uuidMap.put(receiverUuid, entry.getValue());
|
||||||
player.sendMessage(ChatColor.YELLOW + "/friend list - 查看你的好友列表(支持快捷删除)");
|
}
|
||||||
player.sendMessage(ChatColor.GREEN + "==========================");
|
return uuidMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理prepareDelete隐式命令
|
* 启动请求过期检查任务(每1分钟执行一次)
|
||||||
*/
|
*/
|
||||||
private void handlePrepareDeleteCommand(Player player, String[] args) {
|
private void startExpireCheckTask() {
|
||||||
if (args.length < 2) {
|
new BukkitRunnable() {
|
||||||
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/friend prepareDelete <玩家名>");
|
@Override
|
||||||
return;
|
public void run() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 遍历所有待处理请求
|
||||||
|
for (Iterator<Map.Entry<Player, List<FriendRequest>>> iterator = waitingRequests.entrySet().iterator(); iterator.hasNext(); ) {
|
||||||
|
Map.Entry<Player, List<FriendRequest>> entry = iterator.next();
|
||||||
|
Player receiver = entry.getKey();
|
||||||
|
List<FriendRequest> requests = entry.getValue();
|
||||||
|
|
||||||
|
// 移除过期请求
|
||||||
|
Iterator<FriendRequest> reqIterator = requests.iterator();
|
||||||
|
while (reqIterator.hasNext()) {
|
||||||
|
FriendRequest req = reqIterator.next();
|
||||||
|
if (currentTime - req.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
// 通知发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid()));
|
||||||
|
if (sender != null && sender.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "你发送给 " + receiver.getName() + " 的好友请求已过期(5分钟未处理)!");
|
||||||
}
|
}
|
||||||
handlePrepareDelete(player, args[1]);
|
reqIterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若请求列表为空,移除该条目
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存更新后的数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
}.runTaskTimerAsynchronously(plugin, 0L, 20L * 60); // 异步执行,避免阻塞主线程
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,280 @@
|
|||||||
package org.xgqy.survival.command;
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
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;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.scoreboard.Objective;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
public class KillCommandExecutor implements CommandExecutor {
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
private Survival plugin;
|
public class KillCommandExecutor implements CommandExecutor, Listener {
|
||||||
|
// 核心配置常量
|
||||||
|
private static final int EXP_COST = 50; // 经验扣除量
|
||||||
|
private static final long TELEPORT_DELAY = 5 * 20L; // 传送延迟(5秒 = 100tick)
|
||||||
|
private static final long COOLDOWN = 30 * 1000L; // 命令冷却(30秒)
|
||||||
|
private static final double MOVE_THRESHOLD = 0.1; // 移动判定阈值(超过0.1格视为移动)
|
||||||
|
private static final Particle TELEPORT_PARTICLE = Particle.CLOUD;
|
||||||
|
private static final int PARTICLE_COUNT = 25; // 每圈粒子数量
|
||||||
|
private static final double PARTICLE_RADIUS = 1.6; // 粒子生成半径
|
||||||
|
private static final double PARTICLE_Y_OFFSET = 0.6; // 粒子Y轴偏移(避免贴地)
|
||||||
|
private static final Color PARTICLE_COLOR = Color.BLUE; // 粒子颜色(蓝色)
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 正在传送的玩家数据(线程安全)
|
||||||
|
private final Map<Player, TeleportData> teleportingPlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
// 玩家冷却时间记录(线程安全)
|
||||||
|
private final Map<Player, Long> cooldownMap = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
public KillCommandExecutor(Survival plugin) {
|
public KillCommandExecutor(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
// 注册移动事件监听器(检测传送期间是否移动)
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
private static class TeleportData {
|
||||||
|
private final Location startLocation; // 起始位置(用于检测移动)
|
||||||
|
private final int deductedExp; // 已扣除的经验(取消时返还)
|
||||||
|
private boolean hasMoved; // 是否已移动
|
||||||
|
private BukkitTask particleTask; // 粒子效果任务(用于取消)
|
||||||
|
|
||||||
|
public TeleportData(Location startLocation, int deductedExp) {
|
||||||
|
this.startLocation = startLocation;
|
||||||
|
this.deductedExp = deductedExp;
|
||||||
|
this.hasMoved = false;
|
||||||
|
this.particleTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter & Setter
|
||||||
|
public Location getStartLocation() {
|
||||||
|
return startLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeductedExp() {
|
||||||
|
return deductedExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoved() {
|
||||||
|
return hasMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasMoved(boolean hasMoved) {
|
||||||
|
this.hasMoved = hasMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BukkitTask getParticleTask() {
|
||||||
|
return particleTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParticleTask(BukkitTask particleTask) {
|
||||||
|
this.particleTask = particleTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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) sender).damage(11451.0f);
|
if (!(sender instanceof Player player)) {
|
||||||
return true;
|
sender.sendMessage(ChatColor.RED + "❌ 无法对非玩家使用该命令!");
|
||||||
} else {
|
|
||||||
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. 检查冷却状态
|
||||||
|
if (isOnCooldown(player)) {
|
||||||
|
long remainingSeconds = (cooldownMap.get(player) - System.currentTimeMillis()) / 1000;
|
||||||
|
player.sendMessage(ChatColor.RED + "⚠️ 命令冷却中!剩余 " + remainingSeconds + " 秒后可再次使用");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查经验是否足够
|
||||||
|
int totalExp = player.getTotalExperience();
|
||||||
|
if (totalExp < EXP_COST) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 经验不足!使用该命令需消耗 " + EXP_COST + " 点经验,你当前仅拥有 " + totalExp + " 点");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 扣除经验并初始化传送数据
|
||||||
|
player.setTotalExperience(totalExp - EXP_COST);
|
||||||
|
Location startLoc = player.getLocation().clone(); // 记录起始位置(精确到坐标和角度)
|
||||||
|
TeleportData teleportData = new TeleportData(startLoc, EXP_COST);
|
||||||
|
teleportingPlayers.put(player, teleportData);
|
||||||
|
|
||||||
|
// 4. 发送传送提示
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "📡 自动回复活点启动!");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "消耗:" + ChatColor.GREEN + EXP_COST + " 点经验");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "倒计时:" + ChatColor.RED + "5 秒(移动将取消传送)");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
|
|
||||||
|
// 5. 启动粒子效果任务(每0.1秒生成一圈粒子)
|
||||||
|
startParticleEffect(player, teleportData);
|
||||||
|
|
||||||
|
// 6. 启动传送倒计时任务
|
||||||
|
startTeleportTimer(player, teleportData);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测玩家是否在冷却中
|
||||||
|
*/
|
||||||
|
private boolean isOnCooldown(Player player) {
|
||||||
|
if (!cooldownMap.containsKey(player)) return false;
|
||||||
|
// 冷却时间结束后自动移除记录
|
||||||
|
if (System.currentTimeMillis() >= cooldownMap.get(player)) {
|
||||||
|
cooldownMap.remove(player);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动传送粒子效果(围绕玩家生成圆形蓝色粒子)
|
||||||
|
*/
|
||||||
|
private void startParticleEffect(Player player, TeleportData data) {
|
||||||
|
BukkitTask particleTask = new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 玩家已移动或传送结束,停止粒子
|
||||||
|
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location playerLoc = player.getLocation().add(0, PARTICLE_Y_OFFSET, 0);
|
||||||
|
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秒)执行一次
|
||||||
|
|
||||||
|
data.setParticleTask(particleTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动传送倒计时任务
|
||||||
|
*/
|
||||||
|
private void startTeleportTimer(Player player, TeleportData data) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 玩家已移动或传送状态已移除,直接结束
|
||||||
|
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 停止粒子效果
|
||||||
|
if (data.getParticleTask() != null) {
|
||||||
|
data.getParticleTask().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取活点位置(优先床的重生点,无则使用世界出生点)
|
||||||
|
Location spawnLoc = player.getBedSpawnLocation();
|
||||||
|
if (spawnLoc == null) {
|
||||||
|
spawnLoc = player.getWorld().getSpawnLocation().clone().add(0.5, 0, 0.5); // 居中出生点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 执行传送
|
||||||
|
player.teleport(spawnLoc);
|
||||||
|
|
||||||
|
// 4. 保留原功能:设置计分板goback为100
|
||||||
|
Objective gobackObj = player.getScoreboard().getObjective("goback");
|
||||||
|
if (gobackObj != null) {
|
||||||
|
gobackObj.getScore(player.getName()).setScore(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 发送传送成功提示
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "🎉 传送成功!已返回活点");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "提示:" + ChatColor.GRAY + "30秒内无法再次使用该命令");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
|
|
||||||
|
// 6. 设置30秒冷却
|
||||||
|
cooldownMap.put(player, System.currentTimeMillis() + COOLDOWN);
|
||||||
|
|
||||||
|
// 7. 清理传送状态
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, TELEPORT_DELAY); // 5秒后执行传送
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听玩家移动事件(传送期间移动则取消传送)
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
TeleportData data = teleportingPlayers.get(player);
|
||||||
|
|
||||||
|
// 非传送中玩家,直接忽略
|
||||||
|
if (data == null || data.hasMoved()) return;
|
||||||
|
|
||||||
|
Location currentLoc = event.getTo();
|
||||||
|
Location startLoc = data.getStartLocation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查是否移动(距离阈值>0.1格,忽略微小波动)
|
||||||
|
if (currentLoc.distanceSquared(startLoc) > MOVE_THRESHOLD * MOVE_THRESHOLD) {
|
||||||
|
// 标记为已移动,取消传送
|
||||||
|
data.setHasMoved(true);
|
||||||
|
cancelTeleport(player, data);
|
||||||
|
}
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// 避免并发修改异常(忽略)
|
||||||
|
plugin.getLogger().warning("传送期间玩家移动检测并发异常:" + player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消传送并返还经验
|
||||||
|
*/
|
||||||
|
private void cancelTeleport(Player player, TeleportData data) {
|
||||||
|
// 1. 停止粒子效果
|
||||||
|
if (data.getParticleTask() != null) {
|
||||||
|
data.getParticleTask().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 返还扣除的经验
|
||||||
|
player.setTotalExperience(player.getTotalExperience() + data.getDeductedExp());
|
||||||
|
|
||||||
|
// 3. 发送取消提示
|
||||||
|
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 + "=====================================");
|
||||||
|
|
||||||
|
// 4. 清理传送状态
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package org.xgqy.survival.event;
|
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
|
||||||
|
|
||||||
public class AntiExploit implements Listener {
|
|
||||||
|
|
||||||
// 监听玩家执行命令的事件(命令执行前触发)
|
|
||||||
@EventHandler
|
|
||||||
private void onFillCommand(PlayerCommandPreprocessEvent event) {
|
|
||||||
String command = event.getMessage().trim();
|
|
||||||
// 只处理fill命令(避免误判类似/fillxyz的命令)
|
|
||||||
if (!command.startsWith("/fill ")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player player = event.getPlayer();
|
|
||||||
// 分割命令参数(处理多个空格的情况)
|
|
||||||
String[] parts = command.split(" +");
|
|
||||||
// 检查参数是否完整(至少需要:/fill x1 y1 z1 x2 y2 z2)
|
|
||||||
if (parts.length < 7) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取玩家当前位置(用于处理相对坐标~)
|
|
||||||
Location playerLoc = player.getLocation();
|
|
||||||
|
|
||||||
// 解析6个坐标参数(x1, y1, z1, x2, y2, z2)
|
|
||||||
double x1 = parseCoordinate(parts[1], playerLoc.getX());
|
|
||||||
double y1 = parseCoordinate(parts[2], playerLoc.getY());
|
|
||||||
double z1 = parseCoordinate(parts[3], playerLoc.getZ());
|
|
||||||
double x2 = parseCoordinate(parts[4], playerLoc.getX());
|
|
||||||
double y2 = parseCoordinate(parts[5], playerLoc.getY());
|
|
||||||
double z2 = parseCoordinate(parts[6], playerLoc.getZ());
|
|
||||||
|
|
||||||
// 过滤无效坐标(如玩家输入错误格式)
|
|
||||||
if (Double.isNaN(x1) || Double.isNaN(y1) || Double.isNaN(z1) ||
|
|
||||||
Double.isNaN(x2) || Double.isNaN(y2) || Double.isNaN(z2)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算三个轴上的距离(取绝对值)
|
|
||||||
double dx = Math.abs(x2 - x1);
|
|
||||||
double dy = Math.abs(y2 - y1);
|
|
||||||
double dz = Math.abs(z2 - z1);
|
|
||||||
|
|
||||||
// 计算填充的方块总数(体积):(x方向格数) * (y方向格数) * (z方向格数)
|
|
||||||
// 加1是因为包含两个端点坐标的方块
|
|
||||||
long volume = (long) (Math.ceil(dx) + 1) *
|
|
||||||
(long) (Math.ceil(dy) + 1) *
|
|
||||||
(long) (Math.ceil(dz) + 1);
|
|
||||||
|
|
||||||
// 如果体积超过50格,取消命令并提示
|
|
||||||
if (volume > 50) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
player.sendMessage(ChatColor.RED+"警告"+ChatColor.WHITE+" | "+ChatColor.RED+"您有疑似攻击服务器行为!请立即停止!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析坐标(处理相对坐标~和绝对坐标)
|
|
||||||
private double parseCoordinate(String coordStr, double playerCoord) {
|
|
||||||
if (coordStr.startsWith("~")) {
|
|
||||||
// 相对坐标:~表示玩家当前坐标,~10表示当前坐标+10
|
|
||||||
if (coordStr.length() == 1) {
|
|
||||||
return playerCoord; // 只有~,直接返回玩家当前坐标
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// 解析~后面的偏移量(如~-5.2)
|
|
||||||
double offset = Double.parseDouble(coordStr.substring(1));
|
|
||||||
return playerCoord + offset;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return Double.NaN; // 格式错误,返回无效值
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 绝对坐标:直接解析数字
|
|
||||||
try {
|
|
||||||
return Double.parseDouble(coordStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return Double.NaN; // 格式错误,返回无效值
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,13 @@ import org.bukkit.scoreboard.Objective;
|
|||||||
import org.bukkit.scoreboard.Scoreboard;
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ChatEvent implements Listener {
|
public class ChatEvent implements Listener {
|
||||||
private static final String ADMIN_OBJECTIVE = "administrator";
|
private static final String ADMIN_OBJECTIVE = "administrator";
|
||||||
@@ -20,98 +25,203 @@ public class ChatEvent implements Listener {
|
|||||||
private static final long SHORT_INTERVAL_MUTE_TIME = 10000;
|
private static final long SHORT_INTERVAL_MUTE_TIME = 10000;
|
||||||
private static final long SAME_MESSAGE_MUTE_TIME = 15000;
|
private static final long SAME_MESSAGE_MUTE_TIME = 15000;
|
||||||
private static final long CLEAR_INTERVAL = 30000;
|
private static final long CLEAR_INTERVAL = 30000;
|
||||||
|
private static final long FORBIDDEN_MUTE_TIME = 30 * 1000; // 违禁词禁言30秒
|
||||||
|
private static final int CREDIT_DEDUCTION = 5; // 扣除信用分数
|
||||||
|
|
||||||
private Map<Player, Long> lastChatTime = new HashMap<>();
|
// 核心违禁词库(可根据需求扩展,建议从配置文件读取)
|
||||||
private Map<Player, String> lastMessage = new HashMap<>();
|
private static final Set<String> FORBIDDEN_WORDS = new HashSet<>(Arrays.asList(
|
||||||
private Map<Player, Long> muteEndTime = new HashMap<>();
|
"草", "操", "艹", "妈", "他妈", "你妈", "傻逼", "煞笔", "傻屌", "屌", "鸡巴", "jb", "j8",
|
||||||
|
"卖淫", "嫖娼", "毒品", "大麻", "冰毒", "k粉", "邪教", "反动", "分裂", "台独", "港独", "藏独",
|
||||||
|
"日", "干", "操你妈", "cnm", "nmd", "tmd", "mlgb", "草泥马", "草拟吗", "淫", "毒", "邪", "反",
|
||||||
|
"裂", "独", "法西斯", "恐怖", "炸弹", "杀人", "抢劫", "强奸", "赌博", "诈骗", "传销", "邪教组织"
|
||||||
|
));
|
||||||
|
|
||||||
private Survival plugin;
|
// 常见谐音/变体映射(覆盖拼音、符号替换等规避手段)
|
||||||
|
private static final Map<String, String> HOMOPHONE_MAP = new HashMap<>() {{
|
||||||
|
put("cao", "草"); put("ca", "草"); put("cao", "操"); put("艹", "草");
|
||||||
|
put("ma", "妈"); put("mama", "妈妈"); put("mam", "妈");
|
||||||
|
put("gan", "干"); put("gai", "干"); put("ga", "干");
|
||||||
|
put("ri", "日"); put("r", "日");
|
||||||
|
put("tmd", "他妈的"); put("nmd", "你妈的"); put("nm", "你妈");
|
||||||
|
put("sb", "傻逼"); put("sha", "傻"); put("bi", "逼"); put("shabi", "傻逼"); put("煞笔", "傻逼");
|
||||||
|
put("mlgb", "妈了个逼"); put("cnm", "草你妈"); put("caonima", "草你妈");
|
||||||
|
put("草泥马", "草你妈"); put("草拟吗", "草你妈"); put("caoni", "草你");
|
||||||
|
put("diao", "屌"); put("dia", "屌"); put("jb", "鸡巴"); put("j8", "鸡巴"); put("jj", "鸡鸡");
|
||||||
|
put("piao", "嫖"); put("piaochang", "嫖娼"); put("maiyin", "卖淫");
|
||||||
|
put("du", "毒"); put("duming", "毒品"); put("dama", "大麻"); put("bingdu", "冰毒");
|
||||||
|
put("xiejiao", "邪教"); put("fandong", "反动"); put("fenlie", "分裂");
|
||||||
|
put("taidu", "台独"); put("gangdu", "港独"); put("zangdu", "藏独");
|
||||||
|
}};
|
||||||
|
|
||||||
|
// 需过滤的分隔符正则(匹配所有非文字字符,包括空格、符号、数字等)
|
||||||
|
private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[^a-zA-Z\u4e00-\u9fa5]");
|
||||||
|
|
||||||
|
private final Map<Player, Long> lastChatTime = new HashMap<>();
|
||||||
|
private final Map<Player, String> lastMessage = new HashMap<>();
|
||||||
|
private final Map<Player, Long> muteEndTime = new HashMap<>();
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
public ChatEvent(Survival plugin) {
|
public ChatEvent(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
// 启动定时任务,每1秒检查一次
|
// 定时清理历史聊天记录(每30秒)
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
for (Map.Entry<Player, Long> entry : lastChatTime.entrySet()) {
|
Iterator<Map.Entry<Player, Long>> iterator = lastChatTime.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<Player, Long> entry = iterator.next();
|
||||||
|
if (currentTime - entry.getValue() > CLEAR_INTERVAL) {
|
||||||
Player player = entry.getKey();
|
Player player = entry.getKey();
|
||||||
long lastTime = entry.getValue();
|
iterator.remove();
|
||||||
if (currentTime - lastTime > CLEAR_INTERVAL) {
|
|
||||||
lastChatTime.remove(player);
|
|
||||||
lastMessage.remove(player);
|
lastMessage.remove(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.runTaskTimer(this.plugin, 0L, 20L); // 20L表示每1秒执行一次
|
}.runTaskTimer(this.plugin, 0L, 20L * 30); // 30秒执行一次(20tick=1秒)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
|
String originalMessage = event.getMessage().trim();
|
||||||
|
|
||||||
// 检查玩家是否处于禁言状态
|
// 1. 优先检查禁言状态(所有玩家通用)
|
||||||
if (muteEndTime.containsKey(player) && System.currentTimeMillis() < muteEndTime.get(player)) {
|
if (isMuted(player)) {
|
||||||
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
|
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.RED + "禁言 |" + ChatColor.WHITE + " " +
|
ChatColor.RED + "禁言提示 |" + ChatColor.WHITE + " " +
|
||||||
ChatColor.GRAY + "请不要重复发送相同消息 (你需要等待: " +
|
ChatColor.GRAY + "你因违规发言被禁言,剩余时间: " +
|
||||||
ChatColor.DARK_RED + remainingSeconds + "s" +
|
ChatColor.DARK_RED + remainingSeconds + "s"
|
||||||
ChatColor.GRAY + "才能发言)"
|
|
||||||
);
|
);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scoreboard scoreboard = player.getScoreboard();
|
// 2. 严格违禁词检测(管理员也需检查,无豁免)
|
||||||
boolean isAdmin = false;
|
if (containsForbiddenContent(originalMessage)) {
|
||||||
Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE);
|
handleForbiddenViolation(player);
|
||||||
if (adminObj != null && adminObj.getScore(player.getName()).getScore() == 1) {
|
// 广播屏蔽后的消息
|
||||||
isAdmin = true;
|
String maskedMessage = ChatColor.GRAY + player.getPlayerListName() + ": " + ChatColor.RED + "***";
|
||||||
|
player.getServer().broadcastMessage(maskedMessage);
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅非管理员受消息长度限制(管理员忽略此检查)
|
// 3. 管理员判断(后续规则仅普通玩家受限)
|
||||||
String message = event.getMessage();
|
boolean isAdmin = isAdministrator(player);
|
||||||
if (!isAdmin && message.length() > MAX_MESSAGE_LENGTH) {
|
|
||||||
event.setCancelled(true);
|
// 4. 非管理员 - 消息长度限制
|
||||||
|
if (!isAdmin && originalMessage.length() > MAX_MESSAGE_LENGTH) {
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.AQUA + "无法发言 |" + ChatColor.WHITE + ": " +
|
ChatColor.AQUA + "发言限制 |" + ChatColor.WHITE + ": " +
|
||||||
ChatColor.RED + "消息过长(" +
|
ChatColor.RED + "消息过长(非管理员仅可发送" + MAX_MESSAGE_LENGTH + "字符以内)" +
|
||||||
ChatColor.YELLOW + "非管理员仅可发送 30 字符以内的消息(String.message.length=" + message.length() + ")" +
|
ChatColor.GRAY + "(当前长度: " + originalMessage.length() + ")"
|
||||||
ChatColor.RED + ")"
|
|
||||||
);
|
);
|
||||||
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查发言间隔和发言内容
|
// 5. 非管理员 - 发言间隔和重复消息检测
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
|
// 发言速度检测
|
||||||
if (lastChatTime.containsKey(player)) {
|
if (lastChatTime.containsKey(player)) {
|
||||||
long interval = currentTime - lastChatTime.get(player);
|
long interval = currentTime - lastChatTime.get(player);
|
||||||
if (interval < SHORT_INTERVAL) {
|
if (interval < SHORT_INTERVAL) {
|
||||||
muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME);
|
muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME);
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
|
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
|
||||||
ChatColor.GRAY + "你的发言太快了,你需要等待一会儿才能继续发言"
|
ChatColor.GRAY + "发言速度过快,请等待1秒后再试"
|
||||||
);
|
);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(message)) {
|
}
|
||||||
|
// 重复消息检测
|
||||||
|
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(originalMessage)) {
|
||||||
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
|
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
|
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
|
||||||
ChatColor.RED + "请不要发送相同的消息,你需要等待一会儿才能继续发言"
|
ChatColor.GRAY + "禁止发送重复消息,请等待15秒后再试"
|
||||||
);
|
);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
// 更新历史记录
|
||||||
lastChatTime.put(player, currentTime);
|
lastChatTime.put(player, currentTime);
|
||||||
lastMessage.put(player, message);
|
lastMessage.put(player, originalMessage);
|
||||||
}
|
}
|
||||||
String formattedMessage = player.getPlayerListName() + ":" + message;
|
|
||||||
|
// 6. 正常消息格式化发送
|
||||||
|
String formattedMessage = ChatColor.WHITE + player.getPlayerListName() + ": " + ChatColor.GRAY + originalMessage;
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
player.getServer().broadcastMessage(formattedMessage);
|
player.getServer().broadcastMessage(formattedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 严格违禁词检测核心方法(覆盖多种规避手段)
|
||||||
|
* 处理流程:1. 清理文本 → 2. 谐音替换 → 3. 违禁词匹配
|
||||||
|
*/
|
||||||
|
private boolean containsForbiddenContent(String text) {
|
||||||
|
if (text == null || text.isEmpty()) return false;
|
||||||
|
|
||||||
|
// 步骤1:清理文本(移除所有分隔符、数字、符号,保留中英文)
|
||||||
|
String cleanedText = SEPARATOR_PATTERN.matcher(text).replaceAll("");
|
||||||
|
if (cleanedText.isEmpty()) return false;
|
||||||
|
|
||||||
|
// 步骤2:谐音/变体替换(将拼音、符号变体转为核心违禁词)
|
||||||
|
String convertedText = cleanedText.toLowerCase();
|
||||||
|
for (Map.Entry<String, String> entry : HOMOPHONE_MAP.entrySet()) {
|
||||||
|
convertedText = convertedText.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤3:检测是否包含任何违禁词(子串匹配,覆盖组合词)
|
||||||
|
for (String forbiddenWord : FORBIDDEN_WORDS) {
|
||||||
|
if (convertedText.contains(forbiddenWord)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理违禁词违规:扣除信用分 + 30秒禁言 + 提示
|
||||||
|
*/
|
||||||
|
private void handleForbiddenViolation(Player player) {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
// 1. 设置30秒禁言
|
||||||
|
muteEndTime.put(player, currentTime + FORBIDDEN_MUTE_TIME);
|
||||||
|
|
||||||
|
// 2. 扣除5点信用分(同步执行命令,避免异步线程问题)
|
||||||
|
BukkitRunnable runnable = new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String command = "point " + player.getName() + " remove " + CREDIT_DEDUCTION;
|
||||||
|
// 用控制台执行命令,确保权限足够
|
||||||
|
plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), command);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
runnable.runTask(plugin); // 同步到主线程执行
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查玩家是否处于禁言状态
|
||||||
|
*/
|
||||||
|
private boolean isMuted(Player player) {
|
||||||
|
if (!muteEndTime.containsKey(player)) return false;
|
||||||
|
return System.currentTimeMillis() < muteEndTime.get(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查玩家是否为管理员(通过计分板标记)
|
||||||
|
*/
|
||||||
|
private boolean isAdministrator(Player player) {
|
||||||
|
Scoreboard scoreboard = player.getScoreboard();
|
||||||
|
Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE);
|
||||||
|
if (adminObj == null) return false;
|
||||||
|
// 计分板分数为1表示管理员
|
||||||
|
return adminObj.getScore(player.getName()).getScore() == 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
17
src/main/java/org/xgqy/survival/event/ForceSurvival.java
Normal file
17
src/main/java/org/xgqy/survival/event/ForceSurvival.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerGameModeChangeEvent;
|
||||||
|
|
||||||
|
public class ForceSurvival implements Listener {
|
||||||
|
@EventHandler
|
||||||
|
private void gamemodechange(PlayerGameModeChangeEvent event){
|
||||||
|
if(event.getNewGameMode() != GameMode.SURVIVAL){
|
||||||
|
event.getPlayer().setGameMode(GameMode.SURVIVAL);
|
||||||
|
event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.xgqy.survival.event;
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -10,60 +11,125 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
|||||||
import org.xgqy.survival.PlayerTags;
|
import org.xgqy.survival.PlayerTags;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class JoinEvent implements Listener {
|
public class JoinEvent implements Listener {
|
||||||
private Survival plugin;
|
private Survival plugin;
|
||||||
|
|
||||||
|
// 名称违禁词库(覆盖常见违规词汇,含中文、英文、缩写,统一小写存储)
|
||||||
|
private static final Set<String> NAME_FORBIDDEN_WORDS = new HashSet<>(Arrays.asList(
|
||||||
|
"傻逼", "煞笔", "傻屌", "屌", "操", "草", "艹", "妈蛋", "你妈", "他妈",
|
||||||
|
"cnm", "nmd", "tmd", "mlgb", "sb", "shabi", "cao", "gan", "ri",
|
||||||
|
"鸡巴", "jb", "j8", "嫖娼", "卖淫", "毒品", "大麻", "邪教", "反动",
|
||||||
|
"分裂", "台独", "港独", "藏独", "法西斯", "恐怖", "炸弹", "杀人",
|
||||||
|
"抢劫", "强奸", "赌博", "诈骗", "传销", "淫", "毒", "邪", "反", "裂", "独"
|
||||||
|
));
|
||||||
|
|
||||||
public JoinEvent(Survival plugin) {
|
public JoinEvent(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
private void join(PlayerJoinEvent e) {
|
private void join(PlayerJoinEvent e) {
|
||||||
if(plugin.ppoint.getOrDefault(e.getPlayer(),100) <= 0){
|
|
||||||
e.getPlayer().kickPlayer(ChatColor.RED+"您好! 您由于 信用分小于 0 ,我们决定对你的账号 "+ e.getPlayer().getName() +" 采取\n"+ChatColor.RED+ChatColor.BOLD+"永久封禁\n"+ChatColor.RED+"措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n"+ChatColor.RED+"如果您对本次处罚不满,请采取以下措施:\n"+ChatColor.YELLOW+"1.如果您对自己的行为"+ChatColor.BOLD+" 问心无愧 "+ChatColor.YELLOW+"请立即向QQ 2213866559 发送解封申请\n"+ChatColor.YELLOW+"2.如果您属实有违规行为,请"+ChatColor.BOLD+"手写"+ChatColor.YELLOW+"一篇 "+ChatColor.BOLD+"100字以上且AIGC合格"+ChatColor.YELLOW+" 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
|
||||||
}
|
|
||||||
if(plugin.ppoint.getOrDefault(e.getPlayer(),100) == 100){
|
|
||||||
plugin.ppoint.put(e.getPlayer(),100);
|
|
||||||
}
|
|
||||||
PlayerTags playertags = plugin.getPlayerTags();
|
|
||||||
List<String> tags = playertags.getTags(e.getPlayer());
|
|
||||||
if (!tags.isEmpty() && playertags.getCurrentTag(e.getPlayer()) != -1) {
|
|
||||||
e.getPlayer().setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName());
|
|
||||||
e.getPlayer().setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName());
|
|
||||||
} else {
|
|
||||||
if (!tags.isEmpty())
|
|
||||||
e.getPlayer().sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换");
|
|
||||||
}
|
|
||||||
Player player = e.getPlayer();
|
Player player = e.getPlayer();
|
||||||
if(player.getScoreboard().getObjective("logged").getScore(player).getScore() == 0){
|
|
||||||
|
// ########## 新增:名称违禁词检测(优先执行,违规直接踢除)##########
|
||||||
|
if (containsForbiddenWord(player.getName())) {
|
||||||
|
String kickMessage = ChatColor.RED + "=====================================\n" +
|
||||||
|
ChatColor.RED+ "登录失败: 违规用户名\n"+
|
||||||
|
ChatColor.RED + "=====================================";
|
||||||
|
player.kickPlayer(kickMessage);
|
||||||
|
e.setJoinMessage(null); // 取消违规名称的加入广播
|
||||||
|
return; // 终止后续所有逻辑执行
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:强制生存模式
|
||||||
|
if (player.getGameMode() != GameMode.SURVIVAL) {
|
||||||
|
player.setGameMode(GameMode.SURVIVAL);
|
||||||
|
player.sendMessage(ChatColor.AQUA + "星阁钱语 " + ChatColor.WHITE + "| " + ChatColor.RED + "您的游戏模式已经被强制切换为 生存模式");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:信用分<=0 永久封禁
|
||||||
|
if (plugin.ppoint.getOrDefault(player, 100) <= 0) {
|
||||||
|
player.kickPlayer(ChatColor.RED + "您好! 您由于 信用分小于 0 ,我们决定对你的账号 " + player.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:初始化信用分(默认100)
|
||||||
|
if (plugin.ppoint.getOrDefault(player, 100) == 100) {
|
||||||
|
plugin.ppoint.put(player, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:称号设置
|
||||||
|
PlayerTags playertags = plugin.getPlayerTags();
|
||||||
|
List<String> tags = playertags.getTags(player);
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
if (playertags.getCurrentTag(player) != -1) {
|
||||||
|
player.setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换");
|
||||||
|
// 补充:自动设置第一个称号(原逻辑只提示未设置,此处优化体验)
|
||||||
|
playertags.setSelectedTag(player,0);
|
||||||
|
player.setDisplayName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:首次登录随机传送
|
||||||
|
if (player.getScoreboard().getObjective("logged") != null && player.getScoreboard().getObjective("logged").getScore(player).getScore() == 0) {
|
||||||
double x = Math.random() * 10000;
|
double x = Math.random() * 10000;
|
||||||
double z = Math.random() * 10000;
|
double z = Math.random() * 10000;
|
||||||
int bx = (int) x, bz = (int) z;
|
int bx = (int) x, bz = (int) z;
|
||||||
while(true) {
|
|
||||||
if((x <= 150 && x >= -100) || (z <= 150 && z >= -180)){
|
// 修正:原循环逻辑存在死循环风险,优化条件判断
|
||||||
|
while (true) {
|
||||||
|
// 避开出生点区域(-100~150 X/Z范围)
|
||||||
|
boolean inSpawnArea = (x >= -100 && x <= 150) || (z >= -180 && z <= 150);
|
||||||
|
// 确保最高方块不是水/ lava
|
||||||
|
Material highestBlock = player.getWorld().getHighestBlockAt(bx, bz).getType();
|
||||||
|
boolean isDangerous = highestBlock == Material.WATER || highestBlock == Material.LAVA;
|
||||||
|
|
||||||
|
if (inSpawnArea || isDangerous) {
|
||||||
x = Math.random() * 10000;
|
x = Math.random() * 10000;
|
||||||
z = Math.random() * 10000;
|
z = Math.random() * 10000;
|
||||||
bx = (int) x;
|
bx = (int) x;
|
||||||
bz = (int) z;
|
bz = (int) z;
|
||||||
}else if(player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.WATER &&
|
} else {
|
||||||
player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.LAVA){
|
|
||||||
x = Math.random() * 10000;
|
|
||||||
z = Math.random() * 10000;
|
|
||||||
bx = (int) x;
|
|
||||||
bz = (int) z;
|
|
||||||
}else{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.teleport(new Location(player.getWorld(),x,player.getWorld().getHighestBlockYAt(bx,bz),z));
|
|
||||||
//player.sendMessage(ChatColor.GREEN+"你好,欢迎!");
|
int highestY = player.getWorld().getHighestBlockYAt(bx, bz);
|
||||||
|
Location spawnLoc = new Location(player.getWorld(), x, highestY + 1, z); // +1避免卡在方块里
|
||||||
|
player.teleport(spawnLoc);
|
||||||
player.getScoreboard().getObjective("logged").getScore(player).setScore(1);
|
player.getScoreboard().getObjective("logged").getScore(player).setScore(1);
|
||||||
|
player.setRespawnLocation(spawnLoc);
|
||||||
}
|
}
|
||||||
e.setJoinMessage(e.getPlayer().getPlayerListName() + " 加入了 生存1区");
|
|
||||||
e.getPlayer().sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!");
|
// 原有逻辑:加入消息和欢迎提示
|
||||||
e.getPlayer().sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助");
|
e.setJoinMessage(player.getPlayerListName() + " 加入了 生存1区");
|
||||||
e.getPlayer().sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20);
|
player.sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助");
|
||||||
|
player.sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心检测:判断玩家名称是否包含违禁词
|
||||||
|
* 特性:大小写不敏感、忽略空格、子串匹配(覆盖变体规避)
|
||||||
|
*/
|
||||||
|
private boolean containsForbiddenWord(String playerName) {
|
||||||
|
// 预处理:转为小写 + 移除所有空格(应对"傻 逼"这类分隔规避)
|
||||||
|
String processedName = playerName.toLowerCase().replaceAll("\\s+", "");
|
||||||
|
// 遍历违禁词库,检测是否包含子串
|
||||||
|
for (String forbiddenWord : NAME_FORBIDDEN_WORDS) {
|
||||||
|
if (processedName.contains(forbiddenWord)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ commands:
|
|||||||
land:
|
land:
|
||||||
description: change point
|
description: change point
|
||||||
usage: /<command> <player> <set|add|remove> <number>
|
usage: /<command> <player> <set|add|remove> <number>
|
||||||
selfkill:
|
home:
|
||||||
description: self kill
|
description: self kill
|
||||||
usage: /<command>
|
usage: /<command>
|
||||||
notice:
|
notice:
|
||||||
|
|||||||
Reference in New Issue
Block a user