Compare commits

..

5 Commits

Author SHA1 Message Date
4ab0db2f1a update
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 25s
2025-11-08 18:52:48 +08:00
ea8124e0ed Rewritten
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
2025-11-08 13:49:35 +08:00
6f489eb660 Rewritten
Command:
- handle
- report
Added new
Command:
- notice
2025-11-08 11:18:08 +08:00
53fb52e294 Gitea Actions
All checks were successful
Build Minecraft Plugin / Build with Maven (push) Successful in 27s
2025-11-03 14:48:54 +08:00
b2011cecbb Update Maven 2025-11-03 14:47:04 +08:00
21 changed files with 2778 additions and 646 deletions

View File

@@ -0,0 +1,32 @@
name: Build Minecraft Plugin
run-name: ${{ gitea.actor }} is building Minecraft plugin 🚀
on: [push, pull_request]
jobs:
build:
name: Build with Maven
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Set up JDK 21 and enable Maven cache
uses: actions/setup-java@v5
with:
distribution: 'liberica'
java-version: '21'
cache: 'maven'
- name: Set up Maven
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.11
- name: Build and package with Maven
run: mvn -B package
- name: Upload built artifacts
uses: actions/upload-artifact@v3
with:
name: maven-artifacts
path: target/spigot/*.jar

View File

@@ -22,7 +22,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>3.14.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
@@ -31,7 +31,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<version>3.6.1</version>
<executions>
<execution>
<phase>package</phase>
@@ -40,6 +40,11 @@
</goals>
</execution>
</executions>
<configuration>
<finalName>${project.artifactId}-spigot-${project.version}</finalName>
<outputDirectory>${project.build.directory}/spigot</outputDirectory>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</plugin>
</plugins>
<resources>

View File

@@ -8,6 +8,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.xgqy.survival.command.CommandTabExecutor;
import org.xgqy.survival.command.DqshopCommandExecutor;
import org.xgqy.survival.command.FriendCommandExecutor;
import org.xgqy.survival.command.HandleCommandExecutor;
@@ -17,10 +18,13 @@ import org.xgqy.survival.command.InventoryCommandExecutor;
import org.xgqy.survival.command.KillCommandExecutor;
import org.xgqy.survival.command.LandCommandExecutor;
import org.xgqy.survival.command.LoginCommandExecutor;
import org.xgqy.survival.command.MessageBoardCommandExecutor;
import org.xgqy.survival.command.NoticeCommandExecutor;
import org.xgqy.survival.command.PartyCommandExecutor;
import org.xgqy.survival.command.PcCommandExecutor;
import org.xgqy.survival.command.PointCommandExecutor;
import org.xgqy.survival.command.PvpCommandExecutor;
import org.xgqy.survival.command.RankCommandExecutor;
import org.xgqy.survival.command.RegCommandExecutor;
import org.xgqy.survival.command.ReportCommandExecutor;
import org.xgqy.survival.command.SetTagCommandExecutor;
@@ -31,9 +35,11 @@ import org.xgqy.survival.command.TpFinCommandExecutor;
import org.xgqy.survival.command.msgCommandExecutor;
import org.xgqy.survival.event.ChatEvent;
import org.xgqy.survival.event.ChooseTagEvent;
import org.xgqy.survival.event.ForceSurvival;
import org.xgqy.survival.event.JoinEvent;
import org.xgqy.survival.event.LandEvent;
import org.xgqy.survival.event.LoginEvent;
import org.xgqy.survival.event.RankAdd;
import java.io.File;
import java.io.IOException;
@@ -41,6 +47,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -56,6 +63,7 @@ public final class Survival extends JavaPlugin {
public Map<Player, String> banreason = new HashMap<>();
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
// Teleport
private RankAdd contributionManager;
public Map<Player, Player> teleport = new HashMap<>();
public Map<Player, Player> Ateleport = new HashMap<>();
public Map<Player, Location> teleportp = new HashMap<>();
@@ -147,7 +155,6 @@ public final class Survival extends JavaPlugin {
// 信用分数据文件相关
private File ppointFile;
private FileConfiguration ppointConfig;
@Override
public void onEnable() {
// 初始化账号管理器
@@ -173,6 +180,7 @@ public final class Survival extends JavaPlugin {
msg.add(ChatColor.GREEN + "赞助我们? https://starpavilion.xyz/sponsor.html");
msg.add(ChatColor.RED+"封禁者会在此处公示: https://starpavilion.xyz/bans.html");
msg.add(ChatColor.RED + "信用分过低将会导致你的账号受限甚至永久封禁!");
msg.add(ChatColor.GREEN+"小小赞助一下吧~");
msg.add(ChatColor.GREEN + "信用分被扣除会提醒如果您的信用分被莫名扣除请及时到QQ群 717903781 申诉。");
new BukkitRunnable() {
@@ -217,14 +225,21 @@ public final class Survival extends JavaPlugin {
// 根据剩余信用分执行处罚
if (newPoint > 80) {
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群态度诚恳我们将会对你进行解封");
} else if (newPoint <= 20) {
}else if (newPoint <= 10) {
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.getScoreboard().getObjective("check").getScore(plr).setScore(2);
} else if (newPoint <= 80) {
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "监管行为及发言,签到将奖励转换为信用分");
plr.getScoreboard().getObjective("check").getScore(plr).setScore(1);
}
plr.sendMessage(ChatColor.RED + "|为了维护游戏环境,请不要违规!");
plr.sendMessage(ChatColor.RED + "----------------------------");
@@ -250,6 +265,7 @@ public final class Survival extends JavaPlugin {
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
Bukkit.getPluginManager().registerEvents(new LoginEvent(this), this);
Bukkit.getPluginManager().registerEvents(new LandEvent(this),this);
Bukkit.getPluginManager().registerEvents(new ForceSurvival(),this);
// 注册命令执行器
getCommand("report").setExecutor(new ReportCommandExecutor(this));
@@ -271,16 +287,50 @@ public final class Survival extends JavaPlugin {
getCommand("msg").setExecutor(new msgCommandExecutor(this));
getCommand("friend").setExecutor(new FriendCommandExecutor(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("qd").setExecutor(new LandCommandExecutor(this));
getLogger().info("Survival插件已启用信用分持久化功能加载完成");
}
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();
registerTabCompleter(tabExecutor);
}
public RankAdd getContributionManager() {
return contributionManager;
}
private void registerTabCompleter(CommandTabExecutor completer) {
// 所有需要补全的命令列表
List<String> commands = Arrays.asList(
"handle", "tpacc", "friend", "party", "settag", "teleport",
"point", "land", "pvp", "help", "tag", "hub", "home",
"qd", "reg", "login", "msg", "inventory", "notice","messageboard","mb",
"rank"
);
for (String cmd : commands) {
if (getCommand(cmd) != null) {
getCommand(cmd).setTabCompleter(completer);
} else {
getLogger().warning("命令 " + cmd + " 未在 plugin.yml 中配置,无法注册补全器!");
}
}
}
@Override
public void onDisable() {
// 保存玩家标签数据
playerTags.saveTags();
for(Player p : Bukkit.getOnlinePlayers()){
p.kickPlayer(ChatColor.GREEN+"========================================================\n"+
ChatColor.RED+"服务器重启,请稍等 1 - 3 分钟后再次加入\n"+
ChatColor.GREEN+"========================================================");
}
// 保存信用分数据
savePpoint();
@@ -289,7 +339,6 @@ public final class Survival extends JavaPlugin {
try {
FriendCommandExecutor executor = (FriendCommandExecutor) this.getCommand("friend").getExecutor();
if (executor != null) {
executor.saveFriendData();
getLogger().info("好友数据已保存!");
}
} catch (Exception e) {

View File

@@ -0,0 +1,260 @@
package org.xgqy.survival.command;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CommandTabExecutor implements TabCompleter {
private static final Map<String, String> COLOR_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", "");
COLOR_MAP.put("dark_blue", "");
COLOR_MAP.put("dark_green", "");
COLOR_MAP.put("dark_aqua", "");
COLOR_MAP.put("dark_red", "");
COLOR_MAP.put("dark_purple", "");
COLOR_MAP.put("gold", "");
COLOR_MAP.put("gray", "");
COLOR_MAP.put("dark_gray", "");
COLOR_MAP.put("blue", "");
COLOR_MAP.put("green", "");
COLOR_MAP.put("aqua", "");
COLOR_MAP.put("red", "");
COLOR_MAP.put("light_purple", "");
COLOR_MAP.put("yellow", "");
COLOR_MAP.put("white", "");
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
List<String> completions = new ArrayList<>();
String cmdName = command.getName().toLowerCase();
switch (cmdName) {
case "handle":
completions = handleTabComplete(args);
break;
case "tpacc":
completions = tpAccTabComplete(args);
break;
case "friend":
completions = friendTabComplete(sender, args);
break;
case "party":
completions = partyTabComplete(sender, args);
break;
case "settag":
completions = setTagTabComplete(sender, args);
break;
case "teleport":
completions = teleportTabComplete(sender, args);
break;
case "point":
case "land":
completions = pointLandTabComplete(sender, args);
break;
case "pvp":
case "help":
case "tag":
case "hub":
case "home":
case "qd":
case "reg":
case "login":
case "msg":
case "inventory":
case "notice":
completions = simpleCommandTabComplete(sender, cmdName, args);
break;
case "mb":
case "messageboard":
completions = mbTabCompelete(args);
case "rank":
completions = rankTabCompelete((Player) sender,args);
default:
completions.clear();
break;
}
return completions;
}
private List<String> rankTabCompelete(Player sender,String[] args){
List<String> completions = new ArrayList<>();
if(sender.isOp()){
if(args.length == 1){
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) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {
List<String> options = Arrays.asList(
"no", "point", "12h", "1d", "3d", "7d", "14d",
"1mo", "3mo", "1y", "ban"
);
completions = filterMatches(options, args[0]);
}
else if (args.length == 2 && "ban".equalsIgnoreCase(args[0])) {
completions.add("<天数>");
}
return completions;
}
private List<String> tpAccTabComplete(String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {
List<String> options = Arrays.asList("accept", "deny", "tome", "detome");
completions = filterMatches(options, args[0]);
}
return completions;
}
private List<String> friendTabComplete(CommandSender sender, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player)) return completions;
if (args.length == 1) {
List<String> options = Arrays.asList("add", "allow", "deny", "remove", "list");
completions = filterMatches(options, args[0]);
}
else if (args.length == 2 && Arrays.asList("add", "allow", "deny", "remove").contains(args[0].toLowerCase())) {
completions = getOnlinePlayers(args[1]);
}
return completions;
}
private List<String> partyTabComplete(CommandSender sender, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player)) return completions;
if (args.length == 1) {
List<String> options = Arrays.asList(
"create", "quit", "join", "list", "kick", "ban", "disband", "help"
);
completions = filterMatches(options, args[0]);
}
else if (args.length == 2 && Arrays.asList("join", "kick", "ban").contains(args[0].toLowerCase())) {
completions = getOnlinePlayers(args[1]);
}
return completions;
}
private List<String> setTagTabComplete(CommandSender sender, String[] args) {
List<String> completions = new ArrayList<>();
if (!sender.hasPermission("permission.settag")) return completions;
if (args.length == 1) {
completions = getOnlinePlayers(args[1]);
}
else if (args.length == 2) {
List<String> options = Arrays.asList("add", "remove", "check");
completions = filterMatches(options, args[1]);
}
else if (args.length == 3) {
String op = args[1].toLowerCase();
if (Arrays.asList("add", "check", "remove").contains(op)) {
completions.add("<标签内容>");
}
}
else if (args.length == 4) {
String op = args[1].toLowerCase();
if (Arrays.asList("add", "check").contains(op)) {
completions = filterMatches(new ArrayList<>(COLOR_MAP.keySet()), args[3]);
}
}
else if (args.length == 5 && "add".equalsIgnoreCase(args[1])) {
completions.add("<天数(999=永久)>");
}
return completions;
}
private List<String> teleportTabComplete(CommandSender sender, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player)) return completions;
// 一级参数:在线玩家名
if (args.length == 1) {
completions = getOnlinePlayers(args[0]);
}
// 二级参数me
else if (args.length == 2) {
if ("me".startsWith(args[1].toLowerCase())) {
completions.add("me");
}
}
return completions;
}
private List<String> pointLandTabComplete(CommandSender sender, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player)) return completions;
if (args.length == 1) {
completions = getOnlinePlayers(args[0]);
}
else if (args.length == 2) {
List<String> options = Arrays.asList("set", "add", "remove");
completions = filterMatches(options, args[1]);
}
else if (args.length == 3) {
completions.add("<数值>");
}
return completions;
}
private List<String> simpleCommandTabComplete(CommandSender sender, String cmdName, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player)) return completions;
switch (cmdName) {
case "reg":
if (args.length == 1) completions.add("<密码>");
else if (args.length == 2) completions.add("<确认密码>");
break;
case "login":
if (args.length == 1) completions.add("<密码>");
break;
case "msg":
if (args.length == 1) completions = getOnlinePlayers(args[0]);
else if (args.length == 2) completions.add("<消息内容>");
break;
case "inventory":
if (args.length == 1) completions = getOnlinePlayers(args[0]);
break;
case "notice":
if (args.length == 1) completions = getOnlinePlayers(args[0]);
break;
default:
completions.clear();
break;
}
return completions;
}
private static List<String> getOnlinePlayers(String partialName) {
List<String> playerNames = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
playerNames.add(player.getName());
}
return filterMatches(playerNames, partialName);
}
private static List<String> filterMatches(List<String> candidates, String partial) {
String lowerPartial = partial.toLowerCase();
return candidates.stream()
.filter(candidate -> candidate.toLowerCase().startsWith(lowerPartial))
.collect(Collectors.toList());
}
}

View File

@@ -1,9 +1,8 @@
package org.xgqy.survival.command;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import org.bukkit.Bukkit;
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;
@@ -11,476 +10,577 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.FriendData;
import org.xgqy.survival.Survival;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
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 Map<String, BukkitRunnable> timeoutTasks = new ConcurrentHashMap<>();
// 待确认的删除请求
private final Map<Player, Player> pendingDelete = new ConcurrentHashMap<>();
// 删除确认超时任务缓存
private final Map<Player, BukkitRunnable> deleteTimeoutTasks = new ConcurrentHashMap<>();
// 持久化文件
private final File friendDataFile;
// 请求有效期5分钟 = 300000毫秒
private static final long REQUEST_EXPIRE_TIME = 5 * 60 * 1000;
public FriendCommandExecutor(Survival 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() {
return timeoutTasks;
/**
* 好友请求实体存储发送者UUID和时间戳支持序列化
*/
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();
}
// Getter
public String getSenderUuid() {
return senderUuid;
}
public long getTimestamp() {
return timestamp;
}
}
public Map<Player, BukkitRunnable> getDeleteTimeoutTasks() {
return deleteTimeoutTasks;
/**
* 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
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;
}
// 处理隐式命令(点击按钮触发)
if (args.length >= 3 && args[0].equals("confirmDelete")) {
handleConfirmDelete(player, args[1], args[2]);
return true;
}
if (args.length >= 2 && args[0].equals("prepareDelete")) {
handlePrepareDeleteCommand(player, args);
Player player = (Player) sender;
// 无参数时显示帮助
if (args.length == 0) {
showHelp(player);
return true;
}
// 基础参数校验
if (args.length < 1) {
sendUsage(player);
return true;
// 处理子命令
switch (args[0].toLowerCase()) {
case "add":
handleAdd(player, args);
break;
case "remove":
handleRemove(player, args);
break;
case "list":
handleList(player);
break;
case "accept":
handleAccept(player, args);
break;
case "deny":
handleDeny(player, args);
break;
default:
player.sendMessage(prefix + ChatColor.RED + "未知命令!输入 /friend 查看帮助");
}
String subCommand = args[0].toLowerCase();
if (subCommand.equals("list")) {
handleList(player);
return true;
}
if (args.length < 2) {
sendUsage(player);
return true;
}
String targetName = args[1];
Player target = Bukkit.getPlayerExact(targetName); // 精准匹配玩家名(修复)
// 目标玩家有效性校验
if (!subCommand.equals("remove") && (target == null || !target.isOnline())) {
player.sendMessage(ChatColor.RED + "❌ 目标玩家不存在或未在线!");
return true;
}
// 分发子命令
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("好友数据文件不存在,初始化空数据!");
private void handleAdd(Player sender, String[] args) {
// 参数校验
if (args.length != 2) {
sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend add <玩家名>");
return;
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(friendDataFile))) {
FriendData data = (FriendData) ois.readObject();
Map<UUID, List<UUID>> uuidFriends = data.getFriends();
Map<UUID, List<UUID>> uuidWaiting = data.getWaiting();
String targetName = args[1];
Player target = plugin.getServer().getPlayer(targetName);
// 转换好友数据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());
// 目标玩家校验
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<>());
if (senderFriends.contains(target)) {
sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 已是好友关系!");
return;
}
// 检查是否有未过期的请求
List<FriendRequest> targetRequests = waitingRequests.getOrDefault(target, new ArrayList<>());
boolean hasPending = targetRequests.stream()
.anyMatch(req -> req.getSenderUuid().equals(sender.getUniqueId().toString()) &&
System.currentTimeMillis() - req.getTimestamp() < REQUEST_EXPIRE_TIME);
if (hasPending) {
sender.sendMessage(prefix + ChatColor.RED + "已向 " + target.getName() + " 发送请求对方未处理5分钟内不可重复发送");
return;
}
// 发送请求
targetRequests.add(new FriendRequest(sender));
waitingRequests.put(target, targetRequests);
// 通知双方
sender.sendMessage(prefix + ChatColor.GREEN + "好友请求已发送给 " + target.getName() + ",请等待回应~");
target.sendMessage(prefix + ChatColor.YELLOW + "📩 收到 " + sender.getName() + " 的好友请求!");
target.sendMessage(prefix + ChatColor.YELLOW + "使用 /friend accept " + sender.getName() + " 接受,或 /friend deny " + sender.getName() + " 拒绝");
// 保存数据
saveData();
}
/**
* 保存好友数据(使用副本遍历避免并发异常)
* 处理移除好友命令
*/
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 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) {
plugin.friends.computeIfAbsent(player, k -> new ArrayList<>());
List<Player> friends = plugin.friends.get(player);
List<Player> friendList = friends.getOrDefault(player, new ArrayList<>());
if (friends.isEmpty()) {
player.sendMessage(ChatColor.RED + "📭 你的好友列表为空!使用 /friend add <玩家名> 添加好友");
// 空列表提示
if (friendList.isEmpty()) {
player.sendMessage(prefix + ChatColor.GRAY + "你的好友列表为空~ 使用 /friend add <玩家名> 添加好友吧!");
return;
}
player.sendMessage(ChatColor.GREEN + "===== 你的好友列表(共" + friends.size() + "人)=====");
for (Player friend : friends) {
BaseComponent[] friendEntry = new ComponentBuilder()
.append(ChatColor.WHITE + "" + friend.getName())
.append(ChatColor.RED + " [删除]")
.event(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
"/friend prepareDelete " + friend.getName()
))
.create();
player.spigot().sendMessage(friendEntry);
// 显示好友列表(带在线状态)
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);
}
player.sendMessage(ChatColor.GREEN + "======================================");
}
/**
* 准备删除好友(触发二次确认)
* 处理接受好友请求命令
*/
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 + "❌ 该玩家不在你的好友列表中!");
private void handleAccept(Player receiver, String[] args) {
// 参数校验
if (args.length != 2) {
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend accept <请求ID|玩家名>");
return;
}
if (pendingDelete.containsKey(player)) {
player.sendMessage(ChatColor.RED + "❌ 你有未完成的删除确认请求,请先处理!");
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
if (requests.isEmpty()) {
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
return;
}
pendingDelete.put(player, target);
player.sendMessage(ChatColor.YELLOW + "⚠️ 确认要删除好友 " + target.getName() + "10秒内有效");
// 查找目标请求支持ID和玩家名
FriendRequest targetRequest = findRequest(receiver, args[1]);
if (targetRequest == null) {
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!");
return;
}
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);
// 检查请求是否过期
if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) {
removeRequest(receiver, targetRequest);
receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!");
return;
}
// 设置超时任务
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);
// 获取请求发送者
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 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 + "❌ 无对应的删除确认请求!");
private void handleDeny(Player receiver, String[] args) {
// 参数校验
if (args.length != 2) {
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend deny <请求ID|玩家名>");
return;
}
if (deleteTimeoutTasks.containsKey(player)) {
deleteTimeoutTasks.get(player).cancel();
deleteTimeoutTasks.remove(player);
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
if (requests.isEmpty()) {
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
return;
}
if (action.equalsIgnoreCase("yes")) {
handleRemove(player, targetName);
} else if (action.equalsIgnoreCase("no")) {
player.sendMessage(ChatColor.GREEN + "✅ 已取消删除好友 " + target.getName() + "");
// 查找目标请求
FriendRequest targetRequest = findRequest(receiver, args[1]);
if (targetRequest == null) {
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求");
return;
}
pendingDelete.remove(player);
// 检查请求是否过期
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 void handleAdd(Player requester, Player target) {
private FriendRequest findRequest(Player receiver, String param) {
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
// 尝试按ID查找数字
try {
if (requester.equals(target)) {
requester.sendMessage(ChatColor.RED + "❌ 无法添加自己为好友!");
return;
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);
}
plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>());
plugin.friends.computeIfAbsent(target, k -> new ArrayList<>());
plugin.waiting.computeIfAbsent(target, k -> new ArrayList<>());
return null;
}
if (plugin.friends.get(requester).contains(target)) {
requester.sendMessage(ChatColor.RED + "" + target.getName() + " 已是你的好友!");
return;
}
/**
* 移除好友请求
*/
private void removeRequest(Player receiver, FriendRequest request) {
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
requests.remove(request);
if (plugin.waiting.get(target).contains(requester)) {
requester.sendMessage(ChatColor.RED + "⌛ 你已向 " + target.getName() + " 发送过好友请求,等待对方确认中!");
return;
}
if (requests.isEmpty()) {
waitingRequests.remove(receiver);
} else {
waitingRequests.put(receiver, requests);
}
}
if (plugin.waiting.get(requester).contains(target)) {
requester.sendMessage(ChatColor.YELLOW + "📩 " + target.getName() + " 已向你发送好友请求,请使用 /friend allow " + target.getName() + " 同意!");
return;
}
/**
* 显示命令帮助
*/
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分钟超时自动失效");
}
// 发送请求
plugin.waiting.get(target).add(requester);
requester.sendMessage(ChatColor.GREEN + "✅ 好友请求已发送给 " + target.getName() + "对方需在5分钟内确认");
target.sendMessage(ChatColor.AQUA + "📩 收到 " + requester.getName() + " 的好友请求!");
target.sendMessage(ChatColor.AQUA + "使用 /friend allow " + requester.getName() + " 同意,或 /friend deny " + requester.getName() + " 拒绝");
/**
* 加载好友数据从JSON文件
*/
private void loadData() {
if (!dataFile.exists()) {
// 初始化空数据
friends = new ConcurrentHashMap<>();
waitingRequests = new ConcurrentHashMap<>();
return;
}
// 生成按钮
BaseComponent[] message = new ComponentBuilder()
.append(new ComponentBuilder(ChatColor.GREEN + "[同意] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend allow " + requester.getName())).create())
.append(new ComponentBuilder(ChatColor.RED + "[拒绝]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend deny " + requester.getName())).create())
.create();
target.spigot().sendMessage(message);
try (FileReader reader = new FileReader(dataFile)) {
Type dataType = new TypeToken<FriendData>() {}.getType();
FriendData data = gson.fromJson(reader, dataType);
// 超时任务(使用唯一键精准管理
String taskKey = requester.getUniqueId() + "_" + target.getUniqueId();
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);
// 转换UUID映射为Player映射仅加载在线玩家
this.friends = convertUuidToPlayerMap(data.getFriends());
this.waitingRequests = convertUuidToRequestMap(data.getWaitingRequests());
saveFriendData();
} catch (Exception e) {
requester.sendMessage(ChatColor.RED + "❌ 添加好友失败!请联系管理员。");
plugin.getLogger().severe("添加好友异常(" + requester.getName() + " -> " + target.getName() + "" + e.getMessage());
} 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();
}
}
/**
* 处理删除好友命令
* 转换Map<String(UUID), List<String(UUID)>> → Map<Player, List<Player>>
*/
private void handleRemove(Player player, String targetName) {
Player target = Bukkit.getPlayerExact(targetName);
plugin.friends.computeIfAbsent(player, k -> new ArrayList<>());
private Map<Player, List<Player>> convertUuidToPlayerMap(Map<String, List<String>> uuidMap) {
Map<Player, List<Player>> playerMap = new ConcurrentHashMap<>();
if (uuidMap == null) return playerMap;
if (target == null) {
boolean removed = false;
List<Player> friends = plugin.friends.get(player);
for (Player friend : new ArrayList<>(friends)) { // 遍历副本
if (friend.getName().equals(targetName)) {
friends.remove(friend);
plugin.friends.computeIfAbsent(friend, k -> new ArrayList<>()).remove(player);
removed = true;
break;
for (Map.Entry<String, List<String>> entry : uuidMap.entrySet()) {
// 查找在线的玩家(接收者)
Player owner = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
if (owner == null) continue;
// 转换好友列表(仅保留在线玩家)
List<Player> friends = entry.getValue().stream()
.map(uuidStr -> plugin.getServer().getPlayer(UUID.fromString(uuidStr)))
.filter(Objects::nonNull)
.collect(Collectors.toList());
playerMap.put(owner, friends);
}
return playerMap;
}
/**
* 转换Map<Player, List<Player>> → Map<String(UUID), List<String(UUID)>>
*/
private Map<String, List<String>> convertPlayerToUuidMap(Map<Player, List<Player>> playerMap) {
Map<String, List<String>> uuidMap = new ConcurrentHashMap<>();
for (Map.Entry<Player, List<Player>> entry : playerMap.entrySet()) {
String ownerUuid = entry.getKey().getUniqueId().toString();
List<String> friendUuids = entry.getValue().stream()
.map(player -> player.getUniqueId().toString())
.collect(Collectors.toList());
uuidMap.put(ownerUuid, friendUuids);
}
return uuidMap;
}
/**
* 转换Map<String(UUID), List<FriendRequest>> → Map<Player, List<FriendRequest>>
*/
private Map<Player, List<FriendRequest>> convertUuidToRequestMap(Map<String, List<FriendRequest>> uuidMap) {
Map<Player, List<FriendRequest>> playerMap = new ConcurrentHashMap<>();
if (uuidMap == null) return playerMap;
for (Map.Entry<String, List<FriendRequest>> entry : uuidMap.entrySet()) {
Player receiver = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
if (receiver != null) {
playerMap.put(receiver, entry.getValue());
}
}
return playerMap;
}
/**
* 转换Map<Player, List<FriendRequest>> → Map<String(UUID), List<FriendRequest>>
*/
private Map<String, List<FriendRequest>> convertRequestToUuidMap(Map<Player, List<FriendRequest>> playerMap) {
Map<String, List<FriendRequest>> uuidMap = new ConcurrentHashMap<>();
for (Map.Entry<Player, List<FriendRequest>> entry : playerMap.entrySet()) {
String receiverUuid = entry.getKey().getUniqueId().toString();
uuidMap.put(receiverUuid, entry.getValue());
}
return uuidMap;
}
/**
* 启动请求过期检查任务每1分钟执行一次
*/
private void startExpireCheckTask() {
new BukkitRunnable() {
@Override
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分钟未处理");
}
reqIterator.remove();
}
}
// 若请求列表为空,移除该条目
if (requests.isEmpty()) {
iterator.remove();
}
}
// 保存更新后的数据
saveData();
}
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);
plugin.friends.computeIfAbsent(target, k -> new ArrayList<>()).remove(player);
player.sendMessage(ChatColor.GREEN + "✅ 已成功删除好友 " + target.getName() + "");
target.sendMessage(ChatColor.RED + "📢 " + player.getName() + " 已将你从好友列表中移除!");
saveFriendData();
} else {
player.sendMessage(ChatColor.RED + "❌ 你未添加 " + target.getName() + " 为好友!");
}
}
/**
* 处理同意好友请求命令(精准取消超时任务)
*/
private void handleAllow(Player receiver, Player requester) {
plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>());
plugin.friends.computeIfAbsent(receiver, k -> new ArrayList<>());
plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>());
if (!plugin.waiting.get(receiver).contains(requester)) {
receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!");
return;
}
// 双向添加好友
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();
}
/**
* 处理拒绝好友请求命令(精准取消超时任务)
*/
private void handleDeny(Player receiver, Player requester) {
plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>());
if (!plugin.waiting.get(receiver).contains(requester)) {
receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!");
return;
}
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.GRAY + "✅ 已拒绝 " + requester.getName() + " 的好友请求!");
requester.sendMessage(ChatColor.RED + "" + receiver.getName() + " 已拒绝你的好友请求!");
saveFriendData();
}
/**
* 发送命令用法提示
*/
private void sendUsage(Player player) {
player.sendMessage(ChatColor.GREEN + "===== 好友系统命令指南 =====");
player.sendMessage(ChatColor.YELLOW + "/friend add <玩家名> - 向指定玩家发送好友请求");
player.sendMessage(ChatColor.YELLOW + "/friend remove <玩家名> - 从好友列表中删除指定玩家");
player.sendMessage(ChatColor.YELLOW + "/friend allow <玩家名> - 同意指定玩家的好友请求");
player.sendMessage(ChatColor.YELLOW + "/friend deny <玩家名> - 拒绝指定玩家的好友请求");
player.sendMessage(ChatColor.YELLOW + "/friend list - 查看你的好友列表(支持快捷删除)");
player.sendMessage(ChatColor.GREEN + "==========================");
}
/**
* 处理prepareDelete隐式命令
*/
private void handlePrepareDeleteCommand(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/friend prepareDelete <玩家名>");
return;
}
handlePrepareDelete(player, args[1]);
}.runTaskTimerAsynchronously(plugin, 0L, 20L * 60); // 异步执行,避免阻塞主线程
}
}

View File

@@ -11,10 +11,11 @@ import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
import java.util.Date;
import java.util.HashSet;
public class HandleCommandExecutor implements CommandExecutor {
private Survival plugin;
private final Survival plugin;
public HandleCommandExecutor(Survival plugin) {
this.plugin = plugin;
@@ -22,37 +23,254 @@ public class HandleCommandExecutor implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if (args.length != 1) {
sender.sendMessage(ChatColor.RED + "无效语法 :\n/handle <player> - 处理玩家\n/handle list - 待处理列表");
return true;
}
if (args[0].equals("list")) {
sender.sendMessage(ChatColor.YELLOW + "---------------------------------------\n" + ChatColor.GREEN + plugin.banlist.get(sender) + ChatColor.YELLOW + " 原因: ", ChatColor.RED + plugin.banreason.get(plugin.banlist.get(sender)) + ChatColor.YELLOW + "\n---------------------------------------\n");
return true;
}
if (!Bukkit.getPlayer(args[0]).isOnline() && !args[0].equals("list") && Bukkit.getPlayer(args[0]) != plugin.banlist.get((Player) sender)) {
sender.sendMessage(ChatColor.RED + "玩家不在线");
return true;
}
BanList banlis = Bukkit.getBanList(BanList.Type.NAME);
long dur = 24 * 60 * 60 * 1000L;
Date expdat = new Date(System.currentTimeMillis() + dur);
plugin.banreason.put(plugin.banlist.get(sender), null);
plugin.banlist.put((Player) sender, null);
sender.sendMessage(ChatColor.YELLOW + "处理完成\n");
for (Player repo : plugin.reportlist.get(Bukkit.getPlayer(args[0]))) {
repo.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "\n 您举报的玩家 " + ChatColor.RED + ChatColor.BOLD +
args[0] + ChatColor.GREEN + " 已经被封禁\n" + ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | \n" + ChatColor.GREEN + " 感谢您为维护游戏平衡做贡献!");
}
plugin.reportlist.put(Bukkit.getPlayer(args[0]), null);
banlis.addBan(args[0], ChatColor.AQUA + "\n\n星阁钱语\n" + ChatColor.RED + "您的账号 " + args[0] + " 已被封禁\n原因: 管理员处理作弊行为", expdat, null);
Bukkit.getPlayer(args[0]).kickPlayer(ChatColor.AQUA + "星阁钱语\n" + ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " 1 " + ChatColor.RED + "\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + args[0] + ChatColor.RED + "\n封禁原因:" + "管理员处理");
} else {
// 1. 校验执行者必须是在线管理员OP
if (!(sender instanceof Player admin)) {
sender.sendMessage(ChatColor.RED + "只有游戏内管理员可执行此处理命令");
return true;
}
if (!admin.isOp()) {
admin.sendMessage(ChatColor.RED + "权限不足!仅管理员可处理举报");
return true;
}
// 2. 校验管理员是否有待处理的举报
Player target = plugin.banlist.get(admin);
String reportReason = plugin.banreason.get(target);
if (target == null || !target.isOnline()) {
admin.sendMessage(ChatColor.RED + "无待处理的举报玩家,或该玩家已离线");
return true;
}
// 3. 校验参数合法性
if (args.length == 0) {
sendUsage(admin);
return true;
}
// 4. 分支处理不同命令参数(新增带单位的固定封禁)
switch (args[0]) {
case "no":
handleReject(admin, target);
break;
case "point":
handleDeductPoint(admin, target, reportReason);
break;
case "12h":
case "1d":
case "3d":
case "7d":
case "14d":
case "1mo":
case "3mo":
case "1y":
handleFixedBan(admin, target, reportReason, args[0]);
break;
case "ban":
handleCustomBan(admin, target, reportReason, args);
break;
default:
admin.sendMessage(ChatColor.RED + "无效参数!");
sendUsage(admin);
break;
}
return true;
}
/**
* 修正:同步新的命令用法提示(新增带单位的固定封禁参数)
*/
private void sendUsage(Player admin) {
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报处理命令用法:");
admin.sendMessage(ChatColor.WHITE + "/handle no - 驳回举报");
admin.sendMessage(ChatColor.WHITE + "/handle point - 扣除玩家信用分");
admin.sendMessage(ChatColor.WHITE + "/handle 12h/1d/3d/7d/14d - 封禁12小时/1天/3天/7天/14天");
admin.sendMessage(ChatColor.WHITE + "/handle 1mo/3mo/1y - 封禁1个月/3个月/1年");
admin.sendMessage(ChatColor.WHITE + "/handle ban <天数> - 自定义封禁天数(按天计算)");
}
// 原有方法:驳回举报(无修改)
private void handleReject(Player admin, Player target) {
plugin.banlist.remove(admin);
plugin.banreason.remove(target);
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "已驳回玩家 " + ChatColor.RED + target.getName() + " 的举报");
notifyReporters(target, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+target.getName()+ChatColor.GREEN+" 经查实无违规行为,已驳回请求\n"+
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏环境做出贡献,但请不要滥用举报。");
}
// 原有方法:扣除信用分(无修改)
private void handleDeductPoint(Player admin, Player target, String reason) {
plugin.banlist.remove(admin);
plugin.banreason.remove(target);
target.getScoreboard().getObjective("handled").getScore(target).setScore(target.getScoreboard().getObjective(("handled")).getScore(target).getScore()+5);
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "已扣除玩家 " + ChatColor.RED + target.getName() +
ChatColor.GREEN + " 的信用分,举报原因:" + ChatColor.YELLOW + reason);
notifyReporters(target, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+target.getName()+ChatColor.GREEN+" 已经被处理\n"+
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
}
/**
* 修正处理带单位的固定封禁12h/1d/1mo等
*/
private void handleFixedBan(Player admin, Player target, String reason, String timeStr) {
// 调用重载的banPlayer方法支持带单位参数
boolean banSuccess = banPlayer(target, reason, timeStr);
if (banSuccess) {
// 清理待处理数据
plugin.banlist.remove(admin);
plugin.banreason.remove(target);
// 修正通知消息显示真实单位如12小时、1个月
String timeDisplay = getTimeDisplay(timeStr);
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "已成功封禁玩家 " + ChatColor.RED + target.getName() +
ChatColor.GREEN + " " + timeDisplay + ",举报原因:" + ChatColor.YELLOW + reason);
} else {
admin.sendMessage(ChatColor.RED + "封禁失败!无效的封禁时长格式");
}
}
// 原有方法:自定义天数封禁(无修改,仍按天计算)
private void handleCustomBan(Player admin, Player target, String reason, String[] args) {
if (args.length != 2) {
admin.sendMessage(ChatColor.RED + "自定义封禁用法错误!正确格式:/handle ban <天数>");
return;
}
int days;
try {
days = Integer.parseInt(args[1]);
if (days <= 0) {
admin.sendMessage(ChatColor.RED + "封禁天数必须为正整数");
return;
}
} catch (NumberFormatException e) {
admin.sendMessage(ChatColor.RED + "请输入有效的数字作为封禁天数");
return;
}
// 调用原有的banPlayer方法按天数计算
banPlayer(target, reason, days);
plugin.banlist.remove(admin);
plugin.banreason.remove(target);
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "已自定义封禁玩家 " + ChatColor.RED + target.getName() +
ChatColor.GREEN + " " + days + " 天,举报原因:" + ChatColor.YELLOW + reason);
}
/**
* 新增重载banPlayer方法支持带单位的时间字符串
* @param timeStr 带单位的封禁时长12h/1d/3d/7d/14d/1mo/3mo/1y
* @return 封禁是否成功
*/
private boolean banPlayer(Player player, String reason, String timeStr) {
// 1. 解析时间字符串为毫秒数
long banMillis = parseTimeToMillis(timeStr);
if (banMillis <= 0) {
return false;
}
// 2. 计算封禁到期时间
Date expireDate = new Date(System.currentTimeMillis() + banMillis);
// 3. 构建封禁消息(显示真实单位)
String timeDisplay = getTimeDisplay(timeStr);
String banReason = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "您的账号 " + player.getName() + " 已被封禁\n" +
ChatColor.RED + "原因: " + reason + "\n" +
ChatColor.RED + "封禁时长: " + timeDisplay;
String kickMessage = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " " + timeDisplay + " " +
ChatColor.RED + "\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + player.getName() +
ChatColor.RED + "\n封禁原因: " + reason;
// 4. 执行封禁操作
Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), banReason, expireDate, null);
player.kickPlayer(kickMessage);
// 5. 通知举报者
notifyReporters(player, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+player.getName()+ChatColor.GREEN+" 已经被处理\n"+
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
// 6. 清理举报缓存
plugin.reportlist.remove(player);
return true;
}
/**
* 原有:按天数封禁(保留,用于自定义封禁)
*/
private void banPlayer(Player player, String reason, int days) {
long banDuration = (long) days * 24 * 60 * 60 * 1000L;
Date expireDate = new Date(System.currentTimeMillis() + banDuration);
String banReason = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "您的账号 " + player.getName() + " 已被封禁\n" +
ChatColor.RED + "原因: " + reason + "\n" +
ChatColor.RED + "封禁时长: " + days + "";
String kickMessage = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " " + days + " " +
ChatColor.RED + "\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + player.getName() +
ChatColor.RED + "\n封禁原因: " + reason;
Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), banReason, expireDate, null);
player.kickPlayer(kickMessage);
notifyReporters(player, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+player.getName()+ChatColor.GREEN+" 已经被处理\n"+
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
plugin.reportlist.remove(player);
}
/**
* 工具方法:将带单位的时间字符串转换为毫秒数
* 对应关系12h=12小时1d=1天1mo=30天1y=365天
*/
private long parseTimeToMillis(String timeStr) {
return switch (timeStr.toLowerCase()) {
case "12h" -> 12L * 60 * 60 * 1000; // 12小时 = 12*3600*1000ms
case "1d" -> 1L * 24 * 60 * 60 * 1000; // 1天
case "3d" -> 3L * 24 * 60 * 60 * 1000; // 3天
case "7d" -> 7L * 24 * 60 * 60 * 1000; // 7天
case "14d" -> 14L * 24 * 60 * 60 * 1000; // 14天
case "1mo" -> 30L * 24 * 60 * 60 * 1000; // 1个月按30天算
case "3mo" -> 90L * 24 * 60 * 60 * 1000; // 3个月90天
case "1y" -> 365L * 24 * 60 * 60 * 1000; // 1年365天
default -> -1; // 无效格式
};
}
/**
* 工具方法将时间字符串转换为友好显示文本如12h→12小时1mo→1个月
*/
private String getTimeDisplay(String timeStr) {
return switch (timeStr.toLowerCase()) {
case "12h" -> "12小时";
case "1d" -> "1天";
case "3d" -> "3天";
case "7d" -> "7天";
case "14d" -> "14天";
case "1mo" -> "1个月";
case "3mo" -> "3个月";
case "1y" -> "1年";
default -> timeStr; // 异常情况直接显示原字符串
};
}
// 原有方法:通知举报者(无修改)
private void notifyReporters(Player target, String message) {
var reporters = plugin.reportlist.getOrDefault(target, new HashSet<>());
for (Player reporter : reporters) {
if (reporter != null && reporter.isOnline()) {
reporter.sendMessage(message);
}
}
}
}

View File

@@ -28,7 +28,9 @@ public class HelpCommandExecutor implements CommandExecutor {
sender.sendMessage(ChatColor.GREEN+"/party <参数> <参数> - 队伍");
sender.sendMessage(ChatColor.GREEN+"/friend <参数> <参数> - 好友");
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 + "-----------------------------");
return true;
}

View File

@@ -1,29 +1,272 @@
package org.xgqy.survival.command;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
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.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) {
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
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
((Player) sender).damage(11451.0f);
return true;
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
// 仅玩家可使用
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatColor.RED + "X 无法对非玩家使用该命令!");
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 + "X 经验不足!使用该命令需消耗 " + 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.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) {
int k[] = {50};
BukkitTask particleTask = new BukkitRunnable() {
@Override
public void run() {
// 玩家已移动或传送结束,停止粒子
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
player.sendTitle(ChatColor.RED+"传送失败","剩余能量已退还",20,40,20);
this.cancel();
return;
}
player.sendTitle(ChatColor.GREEN+"传送将在 "+String.format("%.1f",1.0*k[0]/10)+" 秒后完成",ChatColor.YELLOW+"已消耗能量: "+(String.format("%d",50-k[0])),0,20,0);
k[0] -= 1;
}
}.runTaskTimer(plugin, 0L, 2L); // 每2tick0.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.sendTitle(ChatColor.GREEN+"传送成功",ChatColor.GREEN+"你现在处于恢复期30秒后才能再次使用",10,50,10);
player.sendMessage(ChatColor.GREEN + "=====================================");
player.sendMessage(ChatColor.GREEN + "传送成功!");
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.RED + "=====================================");
// 4. 清理传送状态
teleportingPlayers.remove(player);
}
}
/*
[投票]
新功能投票
1.【偏实用性】榜单 0
2.【偏技术性】登录检测 0
3.【偏实用性】留言板 1
4.【偏实用性】玩家间购买 0
5.【偏技术性】完善并添加现有功能 0
6.【偏趣味性】打屁限时功能0
7.自己提建议___________
投票输入数字即可。(如果选择7,请附加一句建议)
结束时间: 2025-11-09 17:00
*/

View File

@@ -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和SetterGson序列化必需
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();
}
}
}

View File

@@ -0,0 +1,36 @@
package org.xgqy.survival.command;
import org.bukkit.Bukkit;
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;
public class NoticeCommandExecutor implements CommandExecutor {
private Survival plugin;
public NoticeCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
Player player = Bukkit.getPlayer(args[0]);
if(!player.isOnline()){
sender.sendMessage(ChatColor.RED+"玩家不在线");
return true;
}
if(!player.isOp()){
sender.sendMessage(ChatColor.RED+"你无权使用该命令");
return true;
}
player.sendMessage(ChatColor.RED+"警告"+ChatColor.WHITE+" | "+ChatColor.GRAY+"检测到您有疑似作弊行为,请立即停止。");
player.sendTitle(ChatColor.AQUA+"星阁钱语",ChatColor.RED+"您的行为已上报,正在自动检查。",20,160,20);
//player.playSound(player, Sound.BLOCK_NOTE_BLOCK_XYLOPHONE,1.0f,1.0f);
return true;
}
}

View File

@@ -19,7 +19,6 @@ public class PointCommandExecutor implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if(args.length != 4){
sender.sendMessage(ChatColor.RED+"参数不足或参数过多!\n/point <player> <add|remove|set> <number> <reason>");
return true;
@@ -40,15 +39,11 @@ public class PointCommandExecutor implements CommandExecutor {
}else if(args[1].equals("remove")){
plugin.ppoint.put(player,plugin.ppoint.get(player)-Integer.parseInt(args[2]));
plugin.breason.put(player,args[3]);
player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2]));
player.getScoreboard().getObjective("handled").getScore(player).setScore(player.getScoreboard().getObjective(("handled")).getScore(player).getScore()+Integer.parseInt(args[2]));
}else{
sender.sendMessage(ChatColor.RED+"参数错误!\n/point <player> <add|remove|set> <number> <reason>");
return true;
}
return true;
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
return true;
}
}
}

View 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 + "==============================");
}
}

View File

@@ -1,5 +1,8 @@
package org.xgqy.survival.command;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -317,7 +320,18 @@ public class ReportCommandExecutor implements CommandExecutor, Listener {
ChatColor.GREEN + "举报原因: " + ChatColor.YELLOW + reason + "\n" +
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.YELLOW + "请尽快处理该举报");
BaseComponent[] message = new ComponentBuilder(ChatColor.YELLOW+"工具栏: ")
.append(new ComponentBuilder(ChatColor.GREEN + "[传送到玩家] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tp "+target.getName())).create())
.append(new ComponentBuilder(ChatColor.GREEN + "[提醒该玩家] \n").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/notice "+target.getName())).create())
.append(ChatColor.YELLOW+"处理栏: ")
.append(new ComponentBuilder(ChatColor.RED + "[驳回]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle no")).create())
.append(new ComponentBuilder(ChatColor.RED + "[扣除信用分]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle point")).create())
.append(new ComponentBuilder(ChatColor.RED + "[封禁1天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 1")).create())
.append(new ComponentBuilder(ChatColor.RED + "[封禁3天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 3")).create())
.append(new ComponentBuilder(ChatColor.RED + "[封禁7天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 7")).create())
.append(new ComponentBuilder(ChatColor.RED + "[自定义封禁]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle ban")).create())
.create();
admin.spigot().sendMessage(message);
// 添加到待处理列表
plugin.banlist.put(admin, target);
plugin.banreason.put(target, reason);

View File

@@ -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; // 格式错误,返回无效值
}
}
}
}

View File

@@ -10,8 +10,13 @@ import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.xgqy.survival.Survival;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class ChatEvent implements Listener {
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 SAME_MESSAGE_MUTE_TIME = 15000;
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 Map<Player, Long> muteEndTime = new HashMap<>();
// 核心违禁词库(可根据需求扩展,建议从配置文件读取)
private static final Set<String> FORBIDDEN_WORDS = new HashSet<>(Arrays.asList(
"", "", "", "", "他妈", "你妈", "傻逼", "煞笔", "傻屌", "", "鸡巴", "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) {
this.plugin = plugin;
// 启动定时任务每1秒检查一次
// 定时清理历史聊天记录每30秒
new BukkitRunnable() {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
for (Map.Entry<Player, Long> entry : lastChatTime.entrySet()) {
Player player = entry.getKey();
long lastTime = entry.getValue();
if (currentTime - lastTime > CLEAR_INTERVAL) {
lastChatTime.remove(player);
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();
iterator.remove();
lastMessage.remove(player);
}
}
}
}.runTaskTimer(this.plugin, 0L, 20L); // 20L表示每1秒执行一次
}.runTaskTimer(this.plugin, 0L, 20L * 30); // 30秒执行一次20tick=1秒
}
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
String originalMessage = event.getMessage().trim();
// 检查玩家是否处于禁言状态
if (muteEndTime.containsKey(player) && System.currentTimeMillis() < muteEndTime.get(player)) {
// 1. 优先检查禁言状态(所有玩家通用)
if (isMuted(player)) {
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
player.sendMessage(
ChatColor.RED + "禁言 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "请不要重复发送相同消息 (你需要等待: " +
ChatColor.DARK_RED + remainingSeconds + "s" +
ChatColor.GRAY + "才能发言)"
ChatColor.RED + "禁言提示 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "你因违规发言被禁言,剩余时间: " +
ChatColor.DARK_RED + remainingSeconds + "s"
);
event.setCancelled(true);
return;
}
Scoreboard scoreboard = player.getScoreboard();
boolean isAdmin = false;
Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE);
if (adminObj != null && adminObj.getScore(player.getName()).getScore() == 1) {
isAdmin = true;
// 2. 严格违禁词检测(管理员也需检查,无豁免)
if (containsForbiddenContent(originalMessage)) {
handleForbiddenViolation(player);
// 广播屏蔽后的消息
String maskedMessage = ChatColor.GRAY + player.getPlayerListName() + ": " + ChatColor.RED + "***";
player.getServer().broadcastMessage(maskedMessage);
event.setCancelled(true);
return;
}
// 仅非管理员受消息长度限制(管理员忽略此检查
String message = event.getMessage();
if (!isAdmin && message.length() > MAX_MESSAGE_LENGTH) {
event.setCancelled(true);
// 3. 管理员判断(后续规则仅普通玩家受限
boolean isAdmin = isAdministrator(player);
// 4. 非管理员 - 消息长度限制
if (!isAdmin && originalMessage.length() > MAX_MESSAGE_LENGTH) {
player.sendMessage(
ChatColor.AQUA + "无法发言 |" + ChatColor.WHITE + ": " +
ChatColor.RED + "消息过长(" +
ChatColor.YELLOW + "非管理员仅可发送 30 字符以内的消息(String.message.length=" + message.length() + ")" +
ChatColor.RED + ")"
ChatColor.AQUA + "发言限制 |" + ChatColor.WHITE + ": " +
ChatColor.RED + "消息过长(非管理员仅可发送" + MAX_MESSAGE_LENGTH + "字符以内)" +
ChatColor.GRAY + "(当前长度: " + originalMessage.length() + ")"
);
event.setCancelled(true);
return;
}
// 检查发言间隔和发言内容
// 5. 非管理员 - 发言间隔和重复消息检测
if (!isAdmin) {
long currentTime = System.currentTimeMillis();
// 发言速度检测
if (lastChatTime.containsKey(player)) {
long interval = currentTime - lastChatTime.get(player);
if (interval < SHORT_INTERVAL) {
muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME);
player.sendMessage(
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "你的发言太快了,你需要等待一会儿才能继续发言"
);
event.setCancelled(true);
return;
}
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(message)) {
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
player.sendMessage(
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
ChatColor.RED + "请不要发送相同的消息,你需要等待一会儿才能继续发言"
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "发言速度过快请等待1秒后再试"
);
event.setCancelled(true);
return;
}
}
// 重复消息检测
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(originalMessage)) {
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
player.sendMessage(
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "禁止发送重复消息请等待15秒后再试"
);
event.setCancelled(true);
return;
}
// 更新历史记录
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);
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;
}
}

View File

@@ -0,0 +1,7 @@
package org.xgqy.survival.event;
import org.bukkit.event.Listener;
public class FartExecutor implements Listener {
}

View File

@@ -0,0 +1,18 @@
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.setCancelled(true);
event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式");
}
}
}

View File

@@ -1,6 +1,7 @@
package org.xgqy.survival.event;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@@ -10,60 +11,125 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.xgqy.survival.PlayerTags;
import org.xgqy.survival.Survival;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class JoinEvent implements Listener {
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) {
this.plugin = plugin;
}
@EventHandler
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();
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 z = Math.random() * 10000;
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;
z = Math.random() * 10000;
bx = (int) x;
bz = (int) z;
}else if(player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.WATER &&
player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.LAVA){
x = Math.random() * 10000;
z = Math.random() * 10000;
bx = (int) x;
bz = (int) z;
}else{
} else {
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.setRespawnLocation(spawnLoc);
}
e.setJoinMessage(e.getPlayer().getPlayerListName() + " 加入了 生存1区");
e.getPlayer().sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!");
e.getPlayer().sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助");
e.getPlayer().sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20);
// 原有逻辑:加入消息和欢迎提示
e.setJoinMessage(player.getPlayerListName() + " 加入了 生存1区");
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;
}
}

View File

@@ -13,6 +13,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
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.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
@@ -35,6 +37,11 @@ public class LoginEvent implements Listener {
private final Survival plugin;
// 存储未登录玩家的初始位置(登录后恢复用)
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) {
this.plugin = plugin;
@@ -45,9 +52,17 @@ public class LoginEvent implements Listener {
Player player = event.getPlayer();
Location initialLoc = player.getLocation().clone();
initialLocations.put(player, initialLoc); // 记录初始位置
unloggedPlayerLanded.put(player, false); // 初始化未落地状态
// 初始化未登录隔离状态:隐身+高处传送+致盲
setupUnloggedState(player);
// 新玩家自动添加"小萌新"标签30天
if(!plugin.getAccountManager().isRegistered(player.getUniqueId())){
if(!plugin.getPlayerTags().getTags(player).contains("小萌新")){
plugin.getPlayerTags().addTag(player,"小萌新",30);
}
}
// 登录提示定时器(每秒发送一次,登录后停止)
new BukkitRunnable() {
@Override
@@ -55,32 +70,27 @@ public class LoginEvent implements Listener {
if (plugin.getAccountManager().isLoggedIn(player)) {
// 登录成功,恢复正常状态
restoreLoggedState(player);
initialLocations.remove(player); // 移除缓存的初始位置
initialLocations.remove(player);
unloggedPlayerLanded.remove(player);
this.cancel();
return;
}
// 发送登录/注册提示
// 发送登录/注册提示(修复注册命令格式错误)
if (plugin.getAccountManager().isRegistered(player.getUniqueId())) {
player.sendMessage("§e欢迎回来! 请使用 /login <密码> 登录");
player.sendMessage("§e欢迎回来! 请使用 §a/login <密码> §e登录");
player.sendTitle("§b星阁钱语", "§e请输入 /login <密码> 登录", 0, 100, 0);
} else {
BaseComponent[] message = new ComponentBuilder()
.append(new ComponentBuilder(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)")
// 设置点击事件:复制链接到剪贴板
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html"))
// 设置悬浮事件:鼠标悬停时显示的文字
.event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, // 悬浮动作:显示文本
// 悬浮文字内容(可带颜色)
new ComponentBuilder()
.append(ChatColor.GRAY+"https://starpavilion.xyz/terms.html")
.create()
))
.create())
BaseComponent[] termsMsg = new ComponentBuilder()
.append(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)")
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html"))
.event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder(ChatColor.GRAY + "https://starpavilion.xyz/terms.html").create()
))
.create();
player.spigot().sendMessage(message);
player.sendMessage("§e欢迎来到服务器! 请使用 /reg <密码> <确认密码> 注册");
player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <密码> <确认码> 注册", 0, 100, 0);
player.spigot().sendMessage(termsMsg);
player.sendMessage("§e欢迎来到服务器! 请使用 §a/reg <密码> <确认密码> <确认码> §e注册");
player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <确认密码> <确认码>", 0, 100, 0);
}
}
}.runTaskTimer(plugin, 60, 60); // 1秒发送一次
@@ -91,7 +101,8 @@ public class LoginEvent implements Listener {
public void run() {
if (!plugin.getAccountManager().isLoggedIn(player)) {
initialLocations.remove(player);
player.kickPlayer("§c登录超时");
unloggedPlayerLanded.remove(player);
player.kickPlayer("§c登录超时60秒未操作");
}
}
}.runTaskLater(plugin, 20 * 60);
@@ -103,14 +114,37 @@ public class LoginEvent implements Listener {
// 玩家退出时登出账号并清理缓存
plugin.getAccountManager().logout(player);
initialLocations.remove(player);
unloggedPlayerLanded.remove(player);
}
// ------------------------------ 核心拦截:阻止所有外界交互 ------------------------------
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
// 完全阻止移动(包括位置和视角旋转)
event.setCancelled(true);
Player player = event.getPlayer();
if (!plugin.getAccountManager().isLoggedIn(player)) {
Location from = event.getFrom();
Location to = event.getTo();
if (to == null) {
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已落地请尽快登录或注册");
}
}
}
@@ -118,20 +152,20 @@ public class LoginEvent implements Listener {
public void onPlayerChat(AsyncPlayerChatEvent event) {
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
event.setCancelled(true);
event.getPlayer().sendMessage("§c请先登录或注册");
event.getPlayer().sendMessage("§c请先登录或注册后再聊天");
}
}
@EventHandler
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
String command = event.getMessage().toLowerCase();
// 仅允许登录/注册命令
if (command.startsWith("/reg") || command.startsWith("/login")) {
String command = event.getMessage().toLowerCase().trim();
// 仅允许登录/注册命令(支持带参数的完整命令)
if (command.startsWith("/reg ") || command.startsWith("/login ")) {
return;
}
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
event.setCancelled(true);
event.getPlayer().sendMessage("§c请先登录或注册");
event.getPlayer().sendMessage("§c请先登录或注册(仅允许 /reg /login 命令)");
}
}
@@ -199,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) {
// 1. 隐身(对其他玩家不可见)
player.setInvisible(true);
// 2. 隐藏玩家列表名称(不在Tab列表显示)
// 2. 隐藏Tab列表名称
player.setPlayerListName("");
// 3. 禁用飞行控制(防止移动)
// 3. 禁用飞行(防止异常移动)
player.setAllowFlight(false);
// 4. 传送至当前世界的安全高处Y=255避免掉落后受伤
player.setFlying(false);
// 4. 传送至当前世界高空Y=255确保垂直下落路径安全
Location highLoc = new Location(
player.getWorld(),
player.getLocation().getX(),
@@ -218,47 +298,88 @@ public class LoginEvent implements Listener {
player.getLocation().getZ()
);
player.teleport(highLoc);
// 6. 移除所有可能的干扰效果(保留致盲
// 5. 施加致盲效果(增强隔离体验
player.addPotionEffect(PotionEffectType.BLINDNESS.createEffect(Integer.MAX_VALUE, 0));
// 6. 移除所有干扰效果(保留致盲)
player.getActivePotionEffects().forEach(effect -> {
if (effect.getType() != PotionEffectType.BLINDNESS) {
player.removePotionEffect(effect.getType());
}
});
// 7. 恢复满状态
player.setHealth(player.getMaxHealth());
player.setFoodLevel(20);
}
/**
* 恢复登录玩家的正常状态
*/
/**
* 恢复登录玩家的正常状态
*/
public void restoreLoggedState(Player player) {
// 1. 先设置更长时间的无敌10秒=200tick确保覆盖坠落过程
player.setNoDamageTicks(200);
// 1. 清理未登录状态缓存
unloggedPlayerLanded.remove(player);
// 2. 恢复可见性
player.setInvisible(false);
// 3. 恢复玩家列表名称
// 3. 恢复Tab列表名称带标签
List<String> tags = plugin.getPlayerTags().getTags(player);
int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player);
// 安全处理标签索引(防止数组越界)
int validIndex = Math.max(0, Math.min(currentTagIndex, tags.size() - 1));
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(validIndex) + ChatColor.WHITE + "]" + player.getName());
// 4. 恢复飞行权限
String tag = tags.isEmpty() ? "默认" : tags.get(validIndex);
player.setPlayerListName(ChatColor.WHITE + "[" + tag + ChatColor.WHITE + "]" + player.getName());
// 4. 恢复飞行权限(默认禁用,根据服务器配置调整)
player.setAllowFlight(false);
player.setFlying(false);
// 5. 移除致盲效果
player.removePotionEffect(PotionEffectType.BLINDNESS);
// 6. 传送回初始位置(优先使用安全的重生点)
// 6. 传送回安全初始位置(避免高空危险)
Location initialLoc = initialLocations.getOrDefault(player, player.getWorld().getSpawnLocation());
// 额外检查如果初始位置Y坐标过低比如高空强制使用世界重生点
if (initialLoc.getY() > 200) { // 假设Y>200为危险高空
// 高危位置检测超过200高度强制使用重生点
if (initialLoc.getY() > HIGH_ALTITUDE_SAFE) {
initialLoc = player.getWorld().getSpawnLocation();
}
player.teleport(initialLoc);
// 7. 恢复游戏模式
// 7. 恢复生存模式
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);
}
}

View 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;
}
}

View File

@@ -80,13 +80,26 @@ commands:
land:
description: change point
usage: /<command> <player> <set|add|remove> <number>
selfkill:
home:
description: self kill
usage: /<command>
notice:
description: notice a player
usage: /<command> <player>
messageboard:
description: 留言板命令
usage: /<command> [add|list|support]
aliases: [ mb ]
mb:
description: 留言板命令
usage: /<command> [add|list|support]
permissions:
permission.settag:
description: Allows setting player tags
default: op
permission.handle:
description: handle players reported
default: op
messageboard.admin:
description: admin
default: op