This commit is contained in:
2025-10-12 17:10:23 +08:00
commit 2944f192db
20 changed files with 2535 additions and 0 deletions

View File

@@ -0,0 +1,355 @@
package org.xgqy.survival;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class PlayerTags {
private final Survival plugin;
private final File tagsFile;
// 原有:玩家-称号列表映射
private final Map<UUID, List<String>> playerTags = new HashMap<>();
// 新增:玩家-(称号-到期时间戳映射毫秒级永久用Long.MAX_VALUE
private final Map<UUID, Map<String, Long>> expireTimeMap = new HashMap<>();
// 原有:玩家-选中称号索引映射
public Map<UUID, Integer> playerselectTag = new HashMap<>();
// 常量7天的毫秒数7*24*60*60*1000
private static final long SEVEN_DAYS_MS = 604800000L;
public PlayerTags(Survival plugin) {
this.plugin = plugin;
this.tagsFile = new File(plugin.getDataFolder(), "playertags.data");
}
/**
* 核心辅助:清理玩家的所有过期称号(所有方法调用前先执行)
*/
public void cleanExpiredTags(Player player) {
UUID uuid = player.getUniqueId();
List<String> validTags = new ArrayList<>();
List<String> allTags = playerTags.getOrDefault(uuid, new ArrayList<>());
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
// 遍历所有称号,保留未过期的
for (String tag : allTags) {
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
// 未过期判断永久MAX或当前时间 < 到期时间
if (expireTime == Long.MAX_VALUE || System.currentTimeMillis() < expireTime) {
validTags.add(tag);
} else {
// 过期:移除过期时间记录
tagExpires.remove(tag);
}
}
// 更新有效称号列表和过期时间映射
if (validTags.isEmpty()) {
playerTags.remove(uuid);
expireTimeMap.remove(uuid);
playerselectTag.put(uuid, -1); // 无有效称号,重置选中索引
} else {
playerTags.put(uuid, validTags);
expireTimeMap.put(uuid, tagExpires);
// 检查选中的称号是否已过期:若过期/索引无效,重置索引
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
if (currentIndex == -1 || currentIndex >= validTags.size()) {
playerselectTag.put(uuid, -1);
}
}
}
/**
* 获取当前选中的称号索引(自动过滤过期)
*/
public int getCurrentTag(Player player) {
cleanExpiredTags(player); // 先清理过期
UUID uuid = player.getUniqueId();
Integer index = playerselectTag.get(uuid);
if (index == null) {
return -1;
}
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
playerselectTag.put(uuid, -1); // 索引无效,重置
return -1;
}
return index;
}
/**
* 设置玩家选择的标签索引(自动过滤过期)
*/
public boolean setSelectedTag(Player player, int index) {
cleanExpiredTags(player); // 先清理过期
UUID uuid = player.getUniqueId();
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
// 无有效称号或索引无效,返回失败
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
playerselectTag.put(uuid, -1);
return false;
}
// 设置选中索引,返回成功
playerselectTag.put(uuid, index);
return true;
}
/**
* 加载称号数据核心逻辑——识别旧数据并转为7天过期
*/
@SuppressWarnings("unchecked")
public void loadTags() {
if (!tagsFile.exists()) {
return;
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(tagsFile))) {
// 读取第一个对象判断是旧版本List<String>)还是新版本(含过期时间)
Object firstObj = ois.readObject();
Object secondObj = ois.readObject();
Object thirdObj = null; // 新版本的第三个对象expireTimeMap
// 1. 识别旧版本数据仅2个对象playerTags + playerselectTag
if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?> && ois.available() == 0) {
Map<UUID, List<String>> oldPlayerTags = (Map<UUID, List<String>>) firstObj;
Map<UUID, Integer> oldSelectTag = (Map<UUID, Integer>) secondObj;
// 旧数据转换:为每个称号设置“当前时间+7天”的到期时间
for (Map.Entry<UUID, List<String>> entry : oldPlayerTags.entrySet()) {
UUID uuid = entry.getKey();
List<String> oldTags = entry.getValue();
if (oldTags == null || oldTags.isEmpty()) {
continue;
}
// 为当前玩家创建“称号-到期时间”映射
Map<String, Long> tagExpires = new HashMap<>();
long sevenDaysLater = System.currentTimeMillis() + SEVEN_DAYS_MS; // 7天后到期
for (String tag : oldTags) {
tagExpires.put(tag, sevenDaysLater); // 每个旧称号都设为7天过期
}
// 存入新结构
playerTags.put(uuid, new ArrayList<>(oldTags)); // 复制旧称号列表
expireTimeMap.put(uuid, tagExpires); // 存入7天过期时间
}
// 恢复选中索引
playerselectTag.putAll(oldSelectTag);
plugin.getLogger().info("旧版本称号数据加载完成已自动转为7日后到期");
}
// 2. 识别新版本数据3个对象playerTags + playerselectTag + expireTimeMap
else if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?> && thirdObj instanceof Map<?, ?>) {
playerTags.putAll((Map<UUID, List<String>>) firstObj);
playerselectTag.putAll((Map<UUID, Integer>) secondObj);
expireTimeMap.putAll((Map<UUID, Map<String, Long>>) thirdObj);
plugin.getLogger().info("新版本称号数据加载完成!");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
// 回退逻辑仅加载称号列表所有称号设为7天过期
try (ObjectInputStream fallback = new ObjectInputStream(new FileInputStream(tagsFile))) {
Object obj = fallback.readObject();
if (obj instanceof Map<?, ?> oldPlayerTags) {
for (Map.Entry<?, ?> entry : oldPlayerTags.entrySet()) {
if (entry.getKey() instanceof UUID uuid && entry.getValue() instanceof List<?>) {
List<String> tags = new ArrayList<>();
Map<String, Long> tagExpires = new HashMap<>();
long sevenDaysLater = System.currentTimeMillis() + SEVEN_DAYS_MS;
for (Object tagObj : (List<?>) entry.getValue()) {
if (tagObj instanceof String tag) {
tags.add(tag);
tagExpires.put(tag, sevenDaysLater); // 回退时也设为7天过期
}
}
playerTags.put(uuid, tags);
expireTimeMap.put(uuid, tagExpires);
}
}
playerselectTag.clear();
plugin.getLogger().info("称号数据加载失败已回退并转为7日后到期");
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
/**
* 保存称号数据同步保存playerTags、playerselectTag、expireTimeMap
*/
public void saveTags() {
if (!tagsFile.getParentFile().exists()) {
tagsFile.getParentFile().mkdirs();
}
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(tagsFile))) {
// 按顺序写入3个核心结构新版本
oos.writeObject(playerTags);
oos.writeObject(playerselectTag);
oos.writeObject(expireTimeMap);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取玩家的有效称号列表(自动过滤过期)
*/
public List<String> getTags(Player player) {
cleanExpiredTags(player);
return playerTags.getOrDefault(player.getUniqueId(), new ArrayList<>());
}
/**
* 新增称号:默认永久(可手动指定过期时间)
*
* @param player 目标玩家
* @param tag 称号名称
* @param isPermanent 是否永久true=永久false=7天过期
*/
public void addTag(Player player, String tag, boolean isPermanent) {
cleanExpiredTags(player); // 先清理过期
UUID uuid = player.getUniqueId();
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
// 避免重复添加相同称号
if (!tags.contains(tag)) {
tags.add(tag);
// 设置过期时间:永久=Long.MAX_VALUE临时=当前时间+7天
long expireTime = isPermanent ? Long.MAX_VALUE : System.currentTimeMillis() + SEVEN_DAYS_MS;
tagExpires.put(tag, expireTime);
}
}
/**
* 重载原有addTag方法默认添加永久称号保持向下兼容
*/
public void addTag(Player player, String tag, int days) {
cleanExpiredTags(player); // 先清理过期
UUID uuid = player.getUniqueId();
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
// 避免重复添加相同称号
if (!tags.contains(tag)) {
tags.add(tag);
long expireTime;
if (days == 999) {
expireTime = Long.MAX_VALUE; // 999天视为永久
} else if (days <= 0) {
expireTime = System.currentTimeMillis() - 1; // 天数≤0立即过期触发清理
} else {
// 计算有效期:当前时间 + 天数×24×60×60×1000转换为毫秒
expireTime = System.currentTimeMillis() + (long) days * 24 * 60 * 60 * 1000;
}
tagExpires.put(tag, expireTime);
}
}
/**
* 移除称号:同时删除过期时间记录
*/
public void removeTag(Player player, String tag) {
cleanExpiredTags(player);
UUID uuid = player.getUniqueId();
List<String> tags = playerTags.get(uuid);
Map<String, Long> tagExpires = expireTimeMap.get(uuid);
// 移除称号列表和过期时间映射中的对应条目
if (tags != null) {
tags.remove(tag);
if (tags.isEmpty()) {
playerTags.remove(uuid);
} else {
playerTags.put(uuid, tags);
}
}
if (tagExpires != null) {
tagExpires.remove(tag);
if (tagExpires.isEmpty()) {
expireTimeMap.remove(uuid);
} else {
expireTimeMap.put(uuid, tagExpires);
}
}
// 若移除的是当前选中称号,重置索引
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
if (currentIndex != -1 && (currentIndex >= validTags.size() || !validTags.contains(tag))) {
playerselectTag.put(uuid, -1);
}
}
/**
* 新增:获取称号的到期时间(返回时间戳,单位毫秒)
*
* @return 永久返回-1过期/不存在返回-2有效返回到期时间戳
*/
public long getTagExpireTime(Player player, String tag) {
cleanExpiredTags(player);
UUID uuid = player.getUniqueId();
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
// 称号不存在或已过期,返回-2
if (!validTags.contains(tag)) {
return -2;
}
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
// 永久称号返回-1否则返回时间戳
return expireTime == Long.MAX_VALUE ? -1 : expireTime;
}
/**
* 新增获取称号的剩余时间返回字符串如“2天3小时”
*/
public String getTagRemainingTime(Player player, String tag) {
long expireTime = getTagExpireTime(player, tag);
if (expireTime == -1) {
return "永久有效";
}
if (expireTime == -2) {
return "已过期/不存在";
}
// 计算剩余毫秒数(可能为负,需处理)
long remainingMs = expireTime - System.currentTimeMillis();
if (remainingMs <= 0) {
return "已过期";
}
// 转换为天、时、分
long days = remainingMs / (24 * 60 * 60 * 1000);
long hours = (remainingMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000);
long minutes = (remainingMs % (60 * 60 * 1000)) / (60 * 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("分钟");
return sb.length() > 0 ? sb.toString() : "不足1分钟";
}
}

View File

@@ -0,0 +1,70 @@
package org.xgqy.survival;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.xgqy.survival.command.DqshopCommandExecutor;
import org.xgqy.survival.command.HandleCommandExecutor;
import org.xgqy.survival.command.HelpCommandExecutor;
import org.xgqy.survival.command.HubCommandExecutor;
import org.xgqy.survival.command.PvpCommandExecutor;
import org.xgqy.survival.command.ReportCommandExecutor;
import org.xgqy.survival.command.SetTagCommandExecutor;
import org.xgqy.survival.command.TagCommandExecutor;
import org.xgqy.survival.command.TeleportCommandExecutor;
import org.xgqy.survival.command.TpAccCommandExecutor;
import org.xgqy.survival.command.TpFinCommandExecutor;
import org.xgqy.survival.event.ChatEvent;
import org.xgqy.survival.event.ChooseTagEvent;
import org.xgqy.survival.event.JoinEvent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public final class Survival extends JavaPlugin {
private PlayerTags playerTags;
public Map<Player, Boolean> krt = new HashMap<>();
public Map<Player, Player> banlist = new HashMap<>();
public Map<Player, String> banreason = new HashMap<>();
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
public Map<Player, Player> teleport = new HashMap<>();
public Map<Player, Player> Ateleport = new HashMap<>();
public Map<Player, Location> teleportp = new HashMap<>();
public Map<Player, Integer> isteleport = new HashMap<>();
public Map<Player, Long> lasttp = new HashMap<>();
@Override
public void onEnable() {
// Plugin startup logic
playerTags = new PlayerTags(this);
playerTags.loadTags();
Bukkit.getPluginManager().registerEvents(new JoinEvent(this), this);
Bukkit.getPluginManager().registerEvents(new ChatEvent(this), this);
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
//Bukkit.getPluginManager().registerEvents(new AntiXray(this),this);
getCommand("report").setExecutor(new ReportCommandExecutor(this));
getCommand("handle").setExecutor(new HandleCommandExecutor(this));
getCommand("pvp").setExecutor(new PvpCommandExecutor(this));
getCommand("settag").setExecutor(new SetTagCommandExecutor(this));
getCommand("help").setExecutor(new HelpCommandExecutor(this));
//getCommand("fly").setExecutor(new FlyCommandExecutor(this));
getCommand("tag").setExecutor(new TagCommandExecutor(this));
getCommand("hub").setExecutor(new HubCommandExecutor(this));
getCommand("teleport").setExecutor(new TeleportCommandExecutor(this));
getCommand("tpacc").setExecutor(new TpAccCommandExecutor(this));
getCommand("tpfin").setExecutor(new TpFinCommandExecutor(this));
getCommand("shop").setExecutor(new DqshopCommandExecutor(this));
}
@Override
public void onDisable() {
playerTags.saveTags();
// Plugin shutdown logic
}
public PlayerTags getPlayerTags() {
return playerTags;
}
}

View File

@@ -0,0 +1,186 @@
package org.xgqy.survival.command;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
import java.util.ArrayList;
import java.util.List;
public class DqshopCommandExecutor implements CommandExecutor {
private Survival plugin;
public DqshopCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
Inventory shopGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "商城");
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
ItemMeta headmeta = head.getItemMeta();
List<String> meta1 = new ArrayList<>();
int dq = ((Player) sender).getScoreboard().getObjective("dq").getScore((Player) sender).getScore();
int coin = ((Player) sender).getScoreboard().getObjective("coin").getScore((Player) sender).getScore();
meta1.add(ChatColor.WHITE + "玩家名称: " + ((Player) sender).getPlayerListName());
meta1.add(ChatColor.WHITE + "剩余点卷: " + ChatColor.YELLOW + dq);
meta1.add(ChatColor.WHITE + "剩余金币: " + ChatColor.YELLOW + coin);
headmeta.setLore(meta1);
headmeta.setDisplayName("玩家信息");
head.setItemMeta(headmeta);
shopGui.setItem(4, head);
ItemStack fgx = new ItemStack(Material.RED_STAINED_GLASS_PANE);
ItemMeta metax = fgx.getItemMeta();
List<String> lore = new ArrayList<>();
lore.add("点击退出");
metax.setLore(lore);
fgx.setItemMeta(metax);
for (int i = 9; i < 18; i++) {
shopGui.setItem(i, fgx);
}
((Player) sender).openInventory(shopGui);
ItemStack ch = new ItemStack(Material.NAME_TAG);
ItemMeta metay = ch.getItemMeta();
List<String> lorey = new ArrayList<>();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.YELLOW + "VIP" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 168 点券");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(18, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.YELLOW + "VIP+" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 358 点券");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(19, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.AQUA + "MVP" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 888 点券");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(20, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.AQUA + "MVP+" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 1488 点券");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(21, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.GREEN + "牢玩家" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 358 点券");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(22, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.YELLOW + "自定义称号" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 1288 点券");
lorey.add(ChatColor.GREEN + "点击购买!(1月)");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(23, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.GREEN + "肝-" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 5000 金币");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(24, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.YELLOW + "" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 10000 金币");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(25, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.RED + "肝+" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 15699 金币");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(26, ch);
lorey.clear();
lorey.add(ChatColor.WHITE + "称号 [" + ChatColor.AQUA + "肝++" + ChatColor.WHITE + "]");
lorey.add(ChatColor.YELLOW + "价格: 26999 金币");
lorey.add(ChatColor.GREEN + "点击购买!");
metay.setLore(lorey);
ch.setItemMeta(metay);
shopGui.setItem(27, ch);
ItemStack ch1 = new ItemStack(Material.IRON_INGOT);
ItemMeta metay1 = ch1.getItemMeta();
List<String> lorey1 = new ArrayList<>();
lorey1.add(ChatColor.WHITE + "白银月卡");
lorey1.add(ChatColor.YELLOW + "价格: 12 RMB/月");
lorey1.add(ChatColor.GREEN + "点击购买!");
metay1.setLore(lorey1);
ch1.setItemMeta(metay1);
shopGui.setItem(28, ch1);
ItemStack ch2 = new ItemStack(Material.GOLD_INGOT);
ItemMeta metay2 = ch2.getItemMeta();
List<String> lorey2 = new ArrayList<>();
lorey2.add(ChatColor.GOLD + "黄金月卡");
lorey2.add(ChatColor.YELLOW + "价格: 25 RMB/月");
lorey2.add(ChatColor.GREEN + "点击购买!");
metay2.setLore(lorey2);
ch2.setItemMeta(metay2);
shopGui.setItem(29, ch2);
ch2 = new ItemStack(Material.PAPER);
//ch2.addEnchantment(Enchantment.KNOCKBACK,1);
ItemMeta metay3 = ch2.getItemMeta();
List<String> lorey3 = new ArrayList<>();
lorey3.add(ChatColor.GOLD + "自动解封卡");
lorey3.add(ChatColor.YELLOW + "价格: 128 dq / 月");
lorey3.add(ChatColor.GREEN + "点击购买!");
metay3.setLore(lorey3);
ch2.setItemMeta(metay3);
shopGui.setItem(30, ch2);
ItemStack ft = new ItemStack(Material.WOODEN_PICKAXE);
ItemMeta metayf = ft.getItemMeta();
List<String> loreyf = new ArrayList<>();
loreyf.add(ChatColor.GOLD + "超级木镐");
loreyf.add(ChatColor.YELLOW + "价格: 1000 金币");
loreyf.add(ChatColor.GREEN + "点击购买!(效率 20)");
metayf.setLore(loreyf);
ft.setItemMeta(metayf);
shopGui.setItem(31, ft);
ft = new ItemStack(Material.IRON_PICKAXE);
metayf = ft.getItemMeta();
loreyf.clear();
loreyf.add(ChatColor.WHITE + "超级铁镐");
loreyf.add(ChatColor.YELLOW + "价格: 5000 金币");
loreyf.add(ChatColor.GREEN + "点击购买!(效率 15)");
metayf.setLore(loreyf);
ft.setItemMeta(metayf);
shopGui.setItem(32, ft);
ft = new ItemStack(Material.DIAMOND_SWORD);
metayf = ft.getItemMeta();
loreyf.clear();
loreyf.add(ChatColor.AQUA + "锋利的剑");
loreyf.add(ChatColor.YELLOW + "价格: 10000 金币");
loreyf.add(ChatColor.GREEN + "点击购买!(锋利 10)");
metayf.setLore(loreyf);
ft.setItemMeta(metayf);
shopGui.setItem(33, ft);
} else {
sender.sendMessage(ChatColor.RED + "仅玩家能使用该命令!");
return true;
}
return true;
}
}

View File

@@ -0,0 +1,58 @@
package org.xgqy.survival.command;
import org.bukkit.BanList;
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;
import java.util.Date;
public class HandleCommandExecutor implements CommandExecutor {
private Survival plugin;
public HandleCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@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" + 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 {
return true;
}
return true;
}
}

View File

@@ -0,0 +1,30 @@
package org.xgqy.survival.command;
import org.bukkit.ChatColor;
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;
public class HelpCommandExecutor implements CommandExecutor {
private Survival plugin;
public HelpCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
sender.sendMessage(ChatColor.GREEN + "/pvp - 开启/关闭 玩家伤害");
sender.sendMessage(ChatColor.GREEN + "/tag - 选择称号");
sender.sendMessage(ChatColor.GREEN + "/teleport <玩家名> - 玩家传送");
sender.sendMessage(ChatColor.GREEN + "/report - 举报作弊玩家");
sender.sendMessage(ChatColor.GREEN + "/tpacc <accept|deny> - 同意/拒绝玩家传送");
sender.sendMessage(ChatColor.GREEN + "/tpfin - 结束传送");
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
return true;
}
}

View File

@@ -0,0 +1,65 @@
package org.xgqy.survival.command;
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.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
public class HubCommandExecutor implements CommandExecutor {
private final Survival plugin;
public HubCommandExecutor(Survival plugin) {
this.plugin = plugin;
// 注册Velocity插件消息通道
registerPluginChannels();
}
// 注册Velocity所需的插件消息通道
private void registerPluginChannels() {
// 注册发送通道Velocity接收跨服请求的通道
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "velocity:transfer");
// 可选注册接收通道用于接收Velocity的响应如传送结果
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, "velocity:transfer_ack", new TransferAckListener());
}
@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;
}
String targetServer = "Lobby"; // 目标服务器名称需与Velocity配置中的服务器名一致
// 发送跨服请求到Velocity
sendTransferRequest(player, targetServer);
return true;
}
// 向Velocity发送跨服请求
private void sendTransferRequest(Player player, String targetServer) {
try {
player.kickPlayer(ChatColor.RED + "数据异常: Grim/farteleport");
} catch (Exception e) {
player.sendMessage(ChatColor.RED + "传送请求发送失败!");
plugin.getLogger().severe("发送跨服请求失败: " + e.getMessage());
}
}
// 监听Velocity的传送结果响应可选
private static class TransferAckListener implements PluginMessageListener {
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {
if (!channel.equals("velocity:transfer_ack")) return;
// 解析Velocity返回的传送结果格式是否成功 + 原因)
// 实际解析需根据Velocity的响应格式处理
player.sendMessage(ChatColor.YELLOW + "传送状态已同步");
}
}
}

View File

@@ -0,0 +1,35 @@
package org.xgqy.survival.command;
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 PvpCommandExecutor implements CommandExecutor {
private Survival plugin;
public PvpCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if (plugin.krt.getOrDefault(sender, false) == true) {
plugin.krt.put((Player) sender, false);
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 关");
} else {
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 开");
plugin.krt.put((Player) sender, true);
}
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
return true;
}
return true;
}
}

View File

@@ -0,0 +1,387 @@
package org.xgqy.survival.command;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class ReportCommandExecutor implements CommandExecutor, Listener {
private final Survival plugin;
private final Map<Player, Integer> playerHitCount = new HashMap<>();
private final Map<UUID, UUID> dummyTargetMap = new HashMap<>();
private final Map<UUID, ArmorStand> playerDummies = new HashMap<>(); // 改回存储单个盔甲架
public ReportCommandExecutor(Survival plugin) {
this.plugin = plugin;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "只有玩家可以使用此命令");
return true;
}
Player reporter = (Player) sender;
if (args.length != 2) {
reporter.sendMessage(ChatColor.RED + "无效的指令: 指令语法如下\n/report <玩家名> <原因>");
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null || !target.isOnline()) {
reporter.sendMessage(ChatColor.RED + "玩家不在线");
return true;
}
if (plugin.reportlist.get(target) != null) {
if (plugin.reportlist.get(target).contains(reporter)) {
reporter.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " + ChatColor.RED + "您已经举报过玩家 " + ChatColor.YELLOW + ChatColor.BOLD + args[0] + ChatColor.RED + "请勿重复举报,耐心等待处理");
return true;
}
}
String reason = args[1];
reporter.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "您举报的玩家 " + ChatColor.RED + ChatColor.BOLD +
target.getName() + ChatColor.GREEN + " 正在处理, 请等待处罚结果\n");
// 创建假人并开始监控(在目标玩家位置生成)
createDummyAndMonitor(target);
// 通知管理员
notifyAdmins(reporter, target, reason);
return true;
}
private void createDummyAndMonitor(Player target) {
// 移除现有假人(如果存在)
if (playerDummies.containsKey(target.getUniqueId())) {
ArmorStand existingDummy = playerDummies.get(target.getUniqueId());
if (existingDummy != null && !existingDummy.isDead()) {
existingDummy.remove();
}
playerDummies.remove(target.getUniqueId());
}
// 创建假人实体
ArmorStand dummy = createDummy(target);
if (dummy == null) {
plugin.getLogger().warning("无法为 " + target.getName() + " 创建假人实体");
return;
}
// 存储假人与目标玩家的映射
dummyTargetMap.put(dummy.getUniqueId(), target.getUniqueId());
playerDummies.put(target.getUniqueId(), dummy);
// 启动假人移动任务(围绕目标玩家旋转)
startDummyMovement(dummy, target);
}
private ArmorStand createDummy(Player target) {
try {
// 直接在玩家头顶上方生成假人
Location loc = target.getLocation().add(0, 1.2, 0); // 在玩家头顶上方1.2格的位置
// 创建盔甲架作为假人
ArmorStand dummy = target.getWorld().spawn(loc, ArmorStand.class);
dummy.setCustomName(ChatColor.RED + target.getName());
dummy.setCustomNameVisible(true);
dummy.setVisible(true);
dummy.setGravity(false);
dummy.setInvulnerable(false); // 允许被攻击
dummy.setCollidable(true); // 可碰撞
dummy.setMarker(false); // 有碰撞箱
dummy.setPersistent(false); // 服务器重启时不保存
// 设置假人大小 - 正常大小
dummy.setSmall(false);
// 设置假人头盔为玩家头部
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (meta != null) {
meta.setOwningPlayer(target);
head.setItemMeta(meta);
}
dummy.getEquipment().setHelmet(head);
// 添加发光效果
dummy.addPotionEffect(new PotionEffect(PotionEffectType.GLOWING, 20 * 60 * 5, 1, true, true));
// 设置假人仅对被举报者可见
setDummyVisibility(dummy, target);
return dummy;
} catch (Exception e) {
plugin.getLogger().warning("创建假人失败: " + e.getMessage());
return null;
}
}
// 设置假人可见性 - 仅对被举报者可见
private void setDummyVisibility(ArmorStand dummy, Player target) {
// 对所有玩家隐藏假人
for (Player player : Bukkit.getOnlinePlayers()) {
player.hideEntity(plugin, dummy);
target.showEntity(plugin, dummy);
}
// 仅对被举报者显示
}
private void startDummyMovement(ArmorStand dummy, Player target) {
new BukkitRunnable() {
double angle = 0;
final double radius = 1.2; // 旋转半径
final double height = 0.4; // 垂直偏移
final double speed = 0.5; // 旋转速度
int ticksAlive = 0;
final int maxTicks = 20 * 10; // 20秒用于测试
@Override
public void run() {
ticksAlive++;
// 检查假人是否有效
if (!dummy.isValid() || dummy.isDead()) {
this.cancel();
return;
}
// 检查目标玩家是否在线
if (!target.isOnline()) {
dummy.remove();
this.cancel();
return;
}
// 时间到后自动移除假人
if (ticksAlive >= maxTicks) {
dummy.remove();
dummyTargetMap.remove(dummy.getUniqueId());
playerDummies.remove(target.getUniqueId());
this.cancel();
return;
}
// 更新假人可见性
updateDummyVisibility(dummy, target);
// 使用玩家位置作为中心点
Location center = target.getLocation();
angle += speed;
// 计算圆周运动位置
double x = center.getX() + radius * Math.cos(angle);
double z = center.getZ() + radius * Math.sin(angle);
double y = center.getY() + height + 1.0; // 保持假人在玩家上方
// 设置假人位置
Location dummyLoc = new Location(center.getWorld(), x, y, z);
// 让假人面向中心点
Vector direction = center.toVector().subtract(dummyLoc.toVector());
Location lookAt = dummyLoc.clone();
lookAt.setDirection(direction);
dummy.teleport(lookAt);
}
}.runTaskTimer(plugin, 0, 1);
}
// 更新假人可见性
private void updateDummyVisibility(ArmorStand dummy, Player target) {
// 确保对被举报者可见
if (target.isOnline() && !target.canSee(dummy)) {
target.showEntity(plugin, dummy);
}
// 对其他玩家确保不可见
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.equals(target) && player.canSee(dummy)) {
player.hideEntity(plugin, dummy);
}
}
}
@EventHandler
public void onEntityDamage(EntityDamageByEntityEvent event) {
if (!(event.getDamager() instanceof Player attacker)) return;
Entity victim = event.getEntity();
// 检查是否攻击了假人
if (dummyTargetMap.containsKey(victim.getUniqueId())) {
UUID targetUUID = dummyTargetMap.get(victim.getUniqueId());
Player target = Bukkit.getPlayer(targetUUID);
if (target == null || !target.isOnline()) {
victim.remove();
dummyTargetMap.remove(victim.getUniqueId());
playerDummies.remove(targetUUID);
return;
}
// 只计算真实玩家攻击自己对应的假人
if (attacker.getUniqueId().equals(target.getUniqueId())) {
event.setCancelled(true); // 取消伤害效果,只计数
int hits = playerHitCount.getOrDefault(attacker, 0) + 1;
playerHitCount.put(attacker, hits);
// 显示命中特效
showHitEffect(victim.getLocation());
// 命中8次后封禁玩家
if (hits >= 8) {
attacker.sendMessage(ChatColor.RED + "不公平优势-3300002");
banPlayer(target, "不公平优势");
// 移除假人
ArmorStand dummy = playerDummies.get(target.getUniqueId());
if (dummy != null) {
dummy.remove();
}
dummyTargetMap.remove(victim.getUniqueId());
playerDummies.remove(target.getUniqueId());
playerHitCount.remove(attacker);
// 通知管理员
notifyAdminsForAutoBan(target);
}
}
}
}
private void showHitEffect(Location location) {
// 创建命中特效
location.getWorld().spawnParticle(Particle.END_ROD, location, 20,
new Particle.DustOptions(Color.RED, 2));
location.getWorld().playSound(location, Sound.ENTITY_ARROW_HIT_PLAYER, 1.0f, 1.0f);
}
private void notifyAdmins(Player reporter, Player target, String reason) {
new BukkitRunnable() {
@Override
public void run() {
boolean adminNotified = false;
for (Player admin : Bukkit.getOnlinePlayers()) {
if (admin.isOp()) {
// 通知举报者
reporter.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "您的举报已报告至管理员 " +
ChatColor.LIGHT_PURPLE + admin.getName());
// 通知管理员
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "来自玩家 " + ChatColor.WHITE + reporter.getName() +
ChatColor.GREEN + " 的举报:\n" +
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "被举报者: " + ChatColor.RED + ChatColor.BOLD +
target.getName() + "\n" +
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "举报原因: " + ChatColor.YELLOW + reason + "\n" +
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.YELLOW + "请尽快处理该举报");
// 添加到待处理列表
plugin.banlist.put(admin, target);
plugin.banreason.put(target, reason);
// 更新举报列表
Set<Player> reporters = plugin.reportlist.getOrDefault(target, new HashSet<>());
reporters.add(reporter);
plugin.reportlist.put(target, new HashSet<>(reporters));
adminNotified = true;
}
}
if (!adminNotified) {
reporter.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "当前无管理员在线,已报告至服务器后台");
}
}
}.runTaskLater(plugin, 20L);
}
private void banPlayer(Player player, String reason) {
// 封禁3天
long dur = 3 * 24 * 60 * 60 * 1000L;
Date expireDate = new Date(System.currentTimeMillis() + dur);
String banReason = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "您的账号 " + player.getName() + " 已被封禁\n原因: " + reason;
String kickMessage = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " 3 " +
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.getScoreboard().getObjective("handled").getScore(player.getName()).setScore(1);
player.kickPlayer(kickMessage);
// 通知举报者
Set<Player> reporters = plugin.reportlist.getOrDefault(player, new HashSet<>());
for (Player reporter : reporters) {
if (reporter.isOnline()) {
reporter.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.GREEN + "\n 您举报的玩家 " + ChatColor.RED + ChatColor.BOLD +
player.getName() + ChatColor.GREEN + " 已经被封禁\n" + ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | \n" + ChatColor.GREEN + " 感谢您为维护游戏平衡做贡献!");
}
}
plugin.reportlist.put(player, null);
// 清理数据
plugin.reportlist.remove(player);
// 从管理员待处理列表中移除
for (Map.Entry<Player, Player> entry : new HashSet<>(plugin.banlist.entrySet())) {
if (entry.getValue().equals(player)) {
plugin.banlist.remove(entry.getKey());
}
}
plugin.banreason.remove(player);
}
private void notifyAdminsForAutoBan(Player bannedPlayer) {
for (Player admin : Bukkit.getOnlinePlayers()) {
if (admin.isOp()) {
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
ChatColor.RED + "玩家 " + bannedPlayer.getName() +
" 因多次攻击举报假人已被自动封禁");
}
}
}
}

View File

@@ -0,0 +1,207 @@
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;
import org.xgqy.survival.PlayerTags;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SetTagCommandExecutor implements CommandExecutor {
private final Survival plugin;
private static final Map<String, ChatColor> COLOR_MAP = new HashMap<>();
// 初始化支持的颜色映射
static {
COLOR_MAP.put("black", ChatColor.BLACK);
COLOR_MAP.put("dark_blue", ChatColor.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatColor.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatColor.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatColor.DARK_RED);
COLOR_MAP.put("dark_purple", ChatColor.DARK_PURPLE);
COLOR_MAP.put("gold", ChatColor.GOLD);
COLOR_MAP.put("gray", ChatColor.GRAY);
COLOR_MAP.put("dark_gray", ChatColor.DARK_GRAY);
COLOR_MAP.put("blue", ChatColor.BLUE);
COLOR_MAP.put("green", ChatColor.GREEN);
COLOR_MAP.put("aqua", ChatColor.AQUA);
COLOR_MAP.put("red", ChatColor.RED);
COLOR_MAP.put("light_purple", ChatColor.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatColor.YELLOW);
COLOR_MAP.put("white", ChatColor.WHITE);
}
public SetTagCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// 1. 权限检查
if (!sender.hasPermission("permission.settag")) {
sender.sendMessage(ChatColor.RED + "你没有执行此命令的权限!");
return true;
}
// 2. 参数数量校验
if (args.length < 2) {
sendUsageTip(sender);
return true;
}
String action = args[1].toLowerCase();
// 根据不同操作验证参数数量
if ((action.equals("add") && args.length != 5) ||
(action.equals("remove") && args.length != 3) ||
(action.equals("check") && args.length != 4)) {
sendUsageTip(sender);
return true;
}
// 3. 解析基础参数
String playerName = args[0];
PlayerTags playerTags = plugin.getPlayerTags();
// 4. 验证目标玩家
Player targetPlayer = Bukkit.getPlayer(playerName);
if (targetPlayer == null) {
sender.sendMessage(ChatColor.RED + "玩家 " + playerName + " 不存在或已离线!");
return true;
}
// 5. 按操作类型处理
switch (action) {
case "add":
String tagContentAdd = args[2];
String colorName = args[3].toLowerCase();
String daysStr = args[4];
handleAddTagWithDays(sender, targetPlayer, playerName, tagContentAdd, colorName, daysStr);
break;
case "remove":
String tagContentRemove = args[2];
handleRemoveTag(sender, targetPlayer, playerName, tagContentRemove, playerTags);
break;
case "check":
String tagContentCheck = args[2];
String colorNameCheck = args[3].toLowerCase();
handleCheckTagTime(sender, targetPlayer, playerName, tagContentCheck, colorNameCheck, playerTags);
break;
default:
sender.sendMessage(ChatColor.RED + "无效的操作!仅支持 add/remove/check");
sendUsageTip(sender);
break;
}
return true;
}
/**
* 处理「添加标签(自定义天数)」逻辑
*/
private void handleAddTagWithDays(CommandSender sender, Player targetPlayer, String playerName,
String tagContent, String colorName, String daysStr) {
// 验证颜色
ChatColor tagColor = COLOR_MAP.get(colorName);
if (tagColor == null) {
sender.sendMessage(ChatColor.RED + "无效的颜色!支持的颜色:" + String.join(", ", COLOR_MAP.keySet()));
return;
}
// 构建带颜色的完整标签
String coloredTag = tagColor + tagContent;
// 解析天数参数
int days;
try {
days = Integer.parseInt(daysStr);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "天数格式错误请输入正整数999表示永久");
return;
}
// 调用PlayerTags的方法添加标签
PlayerTags playerTags = plugin.getPlayerTags();
playerTags.addTag(targetPlayer, coloredTag, days);
// 构建有效期提示文本
String expireTip;
if (days == 999) {
expireTip = "永久有效";
} else if (days <= 0) {
expireTip = "立即过期(无效)";
sender.sendMessage(ChatColor.YELLOW + "警告天数≤0会导致标签立即过期");
} else {
expireTip = days + "天有效期";
}
// 发送成功消息
sender.sendMessage(ChatColor.GREEN + "已为玩家 " + playerName + " 添加标签:" + coloredTag + ChatColor.GREEN + "" + expireTip + "");
}
/**
* 处理「删除标签」逻辑(只需要标签内容,不需要颜色)
*/
private void handleRemoveTag(CommandSender sender, Player targetPlayer, String playerName,
String tagContent, PlayerTags playerTags) {
List<String> playerTagList = playerTags.getTags(targetPlayer);
boolean removed = false;
// 遍历所有标签,找到内容匹配的(忽略颜色代码)
for (String tag : new ArrayList<>(playerTagList)) {
// 移除颜色代码后比较内容
String tagWithoutColor = ChatColor.stripColor(tag);
if (tagWithoutColor.equals(tagContent)) {
playerTags.removeTag(targetPlayer, tag);
sender.sendMessage(ChatColor.GREEN + "已从玩家 " + playerName + " 移除标签:" + tag);
removed = true;
// 如果有多个相同内容不同颜色的标签,只删除第一个
break;
}
}
if (!removed) {
sender.sendMessage(ChatColor.RED + "玩家 " + playerName + " 没有找到标签:" + tagContent);
}
}
/**
* 处理「查询标签剩余时间」逻辑
*/
private void handleCheckTagTime(CommandSender sender, Player targetPlayer, String playerName,
String tagContent, String colorName, PlayerTags playerTags) {
// 验证颜色
ChatColor tagColor = COLOR_MAP.get(colorName);
if (tagColor == null) {
sender.sendMessage(ChatColor.RED + "无效的颜色!支持的颜色:" + String.join(", ", COLOR_MAP.keySet()));
return;
}
// 构建带颜色的完整标签
String coloredTag = tagColor + tagContent;
String remainingTime = playerTags.getTagRemainingTime(targetPlayer, coloredTag);
sender.sendMessage(ChatColor.GREEN + "玩家 " + playerName + " 的标签 [" + coloredTag + ChatColor.GREEN + "] 状态:" + remainingTime);
}
/**
* 发送命令用法提示(更新为删除操作无需颜色参数)
*/
private void sendUsageTip(CommandSender sender) {
sender.sendMessage(ChatColor.RED + "命令用法错误!正确格式:");
sender.sendMessage(ChatColor.RED + "1. 添加标签:/settag <玩家名> add <标签内容> <颜色> <天数>");
sender.sendMessage(ChatColor.RED + " - 示例130天/settag XiaoMing add VIP red 30");
sender.sendMessage(ChatColor.RED + " - 示例2永久/settag XiaoHong add 管理员 yellow 999");
sender.sendMessage(ChatColor.RED + "2. 删除标签:/settag <玩家名> remove <标签内容>");
sender.sendMessage(ChatColor.RED + " - 示例:/settag XiaoMing remove VIP");
sender.sendMessage(ChatColor.RED + "3. 查询标签剩余时间:/settag <玩家名> check <标签内容> <颜色>");
sender.sendMessage(ChatColor.RED + " - 示例:/settag XiaoMing check VIP red");
}
}

View File

@@ -0,0 +1,104 @@
package org.xgqy.survival.command;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.PlayerTags;
import org.xgqy.survival.Survival;
import java.util.ArrayList;
import java.util.List;
public class TagCommandExecutor implements CommandExecutor {
private final Survival plugin;
public TagCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// 1. 过滤非玩家执行
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatColor.RED + "只有玩家可使用此命令!");
return true;
}
PlayerTags playerTags = plugin.getPlayerTags();
// 2. 清理玩家过期称号(确保界面只显示有效称号)
playerTags.cleanExpiredTags(player);
List<String> playerValidTags = playerTags.getTags(player); // 获取有效称号列表
int currentSelectedIndex = playerTags.getCurrentTag(player); // 当前选中的称号索引
// 3. 创建称号界面54格标题“称号设置”
Inventory tagGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "称号设置");
// 4. 制作“当前选择”提示物品中间4号槽位
ItemStack currentSelectItem = new ItemStack(Material.NAME_TAG);
ItemMeta currentSelectMeta = currentSelectItem.getItemMeta();
List<String> currentSelectLore = new ArrayList<>();
// 设置“当前选择”的Lore无选中则显示“无”
if (currentSelectedIndex == -1 || playerValidTags.isEmpty()) {
currentSelectLore.add(ChatColor.WHITE + "当前选择: 无");
} else {
String currentTagName = playerValidTags.get(currentSelectedIndex);
currentSelectLore.add(ChatColor.WHITE + "当前选择: [" + currentTagName + ChatColor.WHITE + "]");
}
currentSelectMeta.setLore(currentSelectLore);
currentSelectItem.setItemMeta(currentSelectMeta);
tagGui.setItem(4, currentSelectItem); // 放在界面中间4号槽位
// 5. 制作分隔用的黑色玻璃Pane9-17号槽位一行分隔
ItemStack blackPane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta blackPaneMeta = blackPane.getItemMeta(); // 修复原bug用自己的ItemMeta而非其他物品的
List<String> blackPaneLore = new ArrayList<>();
blackPaneLore.add(ChatColor.WHITE + ""); // 空Lore避免显示默认文本
blackPaneMeta.setLore(blackPaneLore);
blackPane.setItemMeta(blackPaneMeta);
// 填充分隔行9-17号槽位
for (int i = 9; i < 18; i++) {
tagGui.setItem(i, blackPane);
}
// 6. 制作每个有效称号的物品从18号槽位开始排列显示名称+剩余时间+当前选择标记)
for (int i = 0; i < playerValidTags.size(); i++) {
String tagName = playerValidTags.get(i); // 当前循环的称号名称
ItemStack tagItem = new ItemStack(Material.NAME_TAG);
ItemMeta tagMeta = tagItem.getItemMeta();
List<String> tagLore = new ArrayList<>();
// 6.1 添加称号名称(第一行)
tagLore.add(ChatColor.WHITE + tagName);
// 6.2 添加剩余时间第二行调用PlayerTags的方法获取可读时间
String remainingTime = playerTags.getTagRemainingTime(player, tagName);
tagLore.add(ChatColor.GRAY + "剩余时间: " + remainingTime); // 灰色区分,更美观
// 6.3 若当前称号是选中状态,添加“当前选择”标记(第三行)
if (currentSelectedIndex != -1 && tagName.equals(playerValidTags.get(currentSelectedIndex))) {
// 修复原bug用equals比较字符串内容而非====比较引用,会导致判断失效)
tagLore.add(ChatColor.GREEN + "当前选择");
}
// 6.4 赋值Lore并添加到界面
tagMeta.setLore(tagLore);
tagItem.setItemMeta(tagMeta);
tagGui.setItem(18 + i, tagItem); // 从18号槽位开始排列分隔行下方
}
// 7. 打开界面
player.openInventory(tagGui);
return true;
}
}

View File

@@ -0,0 +1,78 @@
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 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.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
public class TeleportCommandExecutor implements CommandExecutor {
private Survival plugin;
public TeleportCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if (System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L) <= 1000 * 60 * 5) {
sender.sendMessage(ChatColor.RED + "传送正在冷却,剩余: " + (4 - (System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000 / 60) + "" + (60 - ((System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000) % 60) + "");
return true;
}
if (args.length != 1) {
sender.sendMessage(ChatColor.RED + "参数错误!用法: /teleport <玩家名>");
return true;
} else {
Player playerto;
if (!Bukkit.getPlayer(args[0]).isOnline()) {
sender.sendMessage(ChatColor.RED + "该玩家不在线!");
return true;
}
playerto = Bukkit.getPlayer(args[0]);
if (plugin.teleport.getOrDefault((Player) sender, null) != null) {
playerto.sendMessage(ChatColor.RED + "您已经向 " + plugin.teleport.get((Player) sender).getName() + ChatColor.RED + " 发送了一份请求");
return true;
}
if (plugin.Ateleport.getOrDefault(playerto, null) != null) {
sender.sendMessage(ChatColor.RED + "对方正在处理另一个请求");
return true;
}
sender.sendMessage(ChatColor.GREEN + "请求已发送");
playerto.sendMessage(ChatColor.GREEN + "玩家 " + sender.getName() + ChatColor.GREEN + " 向你发来传送请求,输入 /tpacc accept 同意这个请求或 /tpacc deny 来拒绝这个请求有效期2分钟。");
BaseComponent[] message = new ComponentBuilder(ChatColor.YELLOW + "玩家 " + sender.getName() + ChatColor.YELLOW + " 向你发来传送请求\n")
.append(new ComponentBuilder(ChatColor.GREEN + "[同意] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tpacc accept")).create())
.append(new ComponentBuilder(ChatColor.RED + "[拒绝]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tpacc deny")).create())
.create();
playerto.spigot().sendMessage(message);
plugin.teleport.put((Player) sender, playerto);
plugin.Ateleport.put(playerto, (Player) sender);
new BukkitRunnable() {
@Override
public void run() {
if (plugin.teleportp.getOrDefault(playerto, null) == null)
playerto.sendMessage(ChatColor.RED + "玩家 " + sender.getName() + ChatColor.RED + " 的传送请求已过期");
if (plugin.teleportp.getOrDefault(playerto, null) == null)
sender.sendMessage(ChatColor.RED + "你向 " + playerto.getName() + ChatColor.RED + " 发送的传送请求已过期");
plugin.teleport.remove((Player) sender);
plugin.Ateleport.remove(playerto);
plugin.isteleport.remove(sender);
}
}.runTaskLater(plugin, 40 * 60);
return true;
}
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
return true;
}
//return true;
}
}

View File

@@ -0,0 +1,66 @@
package org.xgqy.survival.command;
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.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.xgqy.survival.Survival;
public class TpAccCommandExecutor implements CommandExecutor {
private Survival plugin;
public TpAccCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if (plugin.Ateleport.getOrDefault(sender, null) != null) {
if (args[0].contains("accept")) {
sender.sendMessage(ChatColor.GREEN + "传送成功!");
plugin.Ateleport.get(sender).sendMessage(ChatColor.GREEN + "传送成功!输入 /tpfin 回到原处该命令将会在5分钟后过期。");
plugin.teleportp.put(plugin.Ateleport.get(sender), plugin.Ateleport.get(sender).getLocation());
plugin.Ateleport.get(sender).teleport(((Player) sender).getLocation());
plugin.isteleport.put(plugin.Ateleport.get(sender), 1);
new BukkitRunnable() {
@Override
public void run() {
if (plugin.isteleport.get(sender) != 1)
plugin.Ateleport.get(sender).sendMessage(ChatColor.RED + "返回命令已过期,你将无法返回原处!");
plugin.teleportp.remove(plugin.Ateleport.get(sender));
plugin.teleport.remove(plugin.Ateleport.get(sender));
plugin.isteleport.remove(plugin.Ateleport.get(sender));
plugin.Ateleport.remove(sender);
}
}.runTaskLater(plugin, 5 * 20 * 60);
return true;
} else if (args[0].contains("deny")) {
sender.sendMessage(ChatColor.GREEN + "拒绝成功");
plugin.Ateleport.get(sender).sendMessage(ChatColor.GREEN + "您的传送请求被拒绝");
//plugin.teleportp.put(plugin.Ateleport.get(sender),plugin.Ateleport.get(sender).getLocation());
plugin.teleportp.remove(plugin.Ateleport.get(sender));
plugin.teleport.remove(plugin.Ateleport.get(sender));
plugin.isteleport.remove(plugin.Ateleport.get(sender));
plugin.Ateleport.remove(sender);
return true;
} else {
sender.sendMessage(ChatColor.RED + "参数错误,请输入 /tpacc accept 或 /tpacc deny");
return true;
}
} else {
sender.sendMessage(ChatColor.RED + "现在没有传送申请或申请已过期");
return true;
}
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
return true;
}
//return true;
}
}

View File

@@ -0,0 +1,37 @@
package org.xgqy.survival.command;
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 TpFinCommandExecutor implements CommandExecutor {
private Survival plugin;
public TpFinCommandExecutor(Survival plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (sender instanceof Player) {
if (plugin.teleportp.getOrDefault(sender, null) != null) {
((Player) sender).teleport(plugin.teleportp.get(sender));
plugin.lasttp.put((Player) sender, System.currentTimeMillis());
sender.sendMessage(ChatColor.GREEN + "您已返回原处!");
plugin.isteleport.put((Player) sender, 1);
return true;
} else {
sender.sendMessage(ChatColor.RED + "现在没有传送申请或申请已过期");
return true;
}
} else {
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
return true;
}
}
}

View File

@@ -0,0 +1,226 @@
package org.xgqy.survival.event;
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 org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class AntiXray implements Listener {
// 插件实例(用于日志记录和服务器信息获取)
private final Plugin plugin;
// ==================== 核心配置参数(可根据服务器调整)====================
// 1. 时间窗口与阈值配置(双阈值判定)
private static final long WINDOW_1MIN = 60 * 1000L; // 第一个时间窗口1分钟毫秒
private static final int THRESHOLD_1MIN = 5; // 1分钟内阈值5个稀有矿石
private static final long WINDOW_10MIN = 10 * 60 * 1000L;// 第二个时间窗口10分钟毫秒
private static final int THRESHOLD_10MIN = 30; // 10分钟内阈值30个稀有矿石
// 2. 判定为“稀有矿石”的类型(透视玩家主要目标,可增删)
private static final Set<Material> RARE_ORES = new HashSet<>(Arrays.asList(
Material.DIAMOND_ORE,
Material.DEEPSLATE_DIAMOND_ORE,
Material.EMERALD_ORE,
Material.DEEPSLATE_EMERALD_ORE,
Material.GOLD_ORE,
Material.DEEPSLATE_GOLD_ORE,
Material.ANCIENT_DEBRIS // 下界远古残骸(可选,根据服务器是否开放下界调整)
));
// 3. 忽略的游戏模式(避免创造/旁观者模式误判)
private static final Set<String> IGNORED_GAMEMODES = new HashSet<>(Arrays.asList(
"CREATIVE",
"SPECTATOR"
));
// 4. 管理员权限节点(只有拥有该权限的玩家会收到预警)
private static final String ADMIN_PERMISSION = "survival.admin.antixray";
// 存储玩家挖掘记录key=玩家UUIDvalue=该玩家的所有稀有矿石挖掘记录
private final Map<UUID, List<MiningRecord>> playerMiningRecords = new ConcurrentHashMap<>();
// 挖掘记录实体类:存储矿石类型和挖掘时间戳
private static class MiningRecord {
private final Material oreType; // 挖掘的矿石类型
private final long timestamp; // 挖掘时间戳(毫秒)
public MiningRecord(Material oreType, long timestamp) {
this.oreType = oreType;
this.timestamp = timestamp;
}
public Material getOreType() {
return oreType;
}
public long getTimestamp() {
return timestamp;
}
}
// 构造方法:必须传入插件实例(用于日志和权限验证)
public AntiXray(Plugin plugin) {
this.plugin = plugin;
}
// 监听方块破坏事件:仅处理稀有矿石的挖掘
@EventHandler
private void onRareOreBreak(BlockBreakEvent e) {
Player player = e.getPlayer();
Material brokenBlock = e.getBlock().getType();
// 1. 过滤无效场景(忽略的游戏模式、非稀有矿石)
if (IGNORED_GAMEMODES.contains(player.getGameMode().name())
|| !RARE_ORES.contains(brokenBlock)) {
return;
}
// 2. 处理玩家挖掘记录(添加新记录 + 清理过期记录)
UUID playerUuid = player.getUniqueId();
// 玩家无记录则初始化空列表,有记录则直接获取
List<MiningRecord> records = playerMiningRecords.computeIfAbsent(playerUuid, k -> new ArrayList<>());
// 添加当前挖掘记录(当前时间戳)
records.add(new MiningRecord(brokenBlock, System.currentTimeMillis()));
// 清理过期记录只保留10分钟内的超过10分钟的记录对双阈值都无意义
long currentTime = System.currentTimeMillis();
records.removeIf(record -> currentTime - record.getTimestamp() > WINDOW_10MIN);
// 3. 统计两个时间窗口内的稀有矿石数量
int count1Min = countOresInWindow(records, WINDOW_1MIN); // 1分钟内数量
int count10Min = countOresInWindow(records, WINDOW_10MIN);// 10分钟内数量
// 4. 双阈值判定:满足任一阈值则发送管理员预警
if (count1Min >= THRESHOLD_1MIN || count10Min >= THRESHOLD_10MIN) {
sendAdminWarning(player, count1Min, count10Min);
// 记录服务器日志(便于后续追溯)
logWarning(player, count1Min, count10Min);
}
}
/**
* 统计指定时间窗口内的稀有矿石数量
*
* @param records 玩家的挖掘记录列表
* @param windowMs 时间窗口(毫秒)
* @return 时间窗口内的稀有矿石总数
*/
private int countOresInWindow(List<MiningRecord> records, long windowMs) {
long currentTime = System.currentTimeMillis();
// 过滤出“当前时间 - 时间窗口”之后的记录,统计数量
return (int) records.stream()
.filter(record -> currentTime - record.getTimestamp() <= windowMs)
.count();
}
/**
* 向所有在线管理员发送预警信息
*
* @param player 疑似透视的玩家
* @param count1Min 1分钟内挖掘数量
* @param count10Min 10分钟内挖掘数量
*/
private void sendAdminWarning(Player player, int count1Min, int count10Min) {
// 构建预警消息(用颜色区分关键信息,便于管理员快速识别)
String warningMsg = ChatColor.RED + "[AntiXray预警] " + ChatColor.YELLOW
+ "玩家 " + ChatColor.WHITE + player.getName()
+ ChatColor.YELLOW + " 疑似透视:"
+ ChatColor.RED + "1分钟内:" + ChatColor.WHITE + count1Min
+ ChatColor.RED + " 10分钟内:" + ChatColor.WHITE + count10Min;
BaseComponent[] message = new ComponentBuilder(warningMsg)
.append(new ComponentBuilder(ChatColor.GREEN + "\n[传送到该玩家] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tp " + player.getName())).create())
.append(new ComponentBuilder(ChatColor.RED + "[处理该玩家]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/warn " + player.getName() + " X-Ray")).create())
.create();
// 遍历所有在线玩家,仅向拥有管理员权限的玩家发送消息
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.hasPermission(ADMIN_PERMISSION)) {
onlinePlayer.spigot().sendMessage(message);
}
}
// 同时向控制台发送预警(确保离线管理员后续能看到日志)
Bukkit.getConsoleSender().sendMessage(warningMsg);
}
/**
* 记录预警日志到服务器日志文件
*
* @param player 疑似透视的玩家
* @param count1Min 1分钟内挖掘数量
* @param count10Min 10分钟内挖掘数量
*/
private void logWarning(Player player, int count1Min, int count10Min) {
String logMsg = String.format(
"[AntiXray] 疑似透视行为 - 玩家:%sUUID%s1分钟内矿石数%d阈值%d10分钟内矿石数%d阈值%d时间%s",
player.getName(),
player.getUniqueId(),
count1Min,
THRESHOLD_1MIN,
count10Min,
THRESHOLD_10MIN,
new Date() // 记录当前时间
);
plugin.getLogger().warning(logMsg);
}
// ==================== (可选)管理员工具方法 ====================
/**
* 手动清理指定玩家的挖掘记录(用于管理员确认无违规后重置状态)
*
* @param player 目标玩家
*/
public void clearPlayerRecord(Player player) {
if (player == null) return;
playerMiningRecords.remove(player.getUniqueId());
plugin.getLogger().info("已清理玩家 " + player.getName() + " 的AntiXray挖掘记录");
}
/**
* 获取当前所有玩家的挖掘记录统计(用于管理员调试或核查)
*
* @return 格式化的统计信息
*/
public String getMiningStats() {
if (playerMiningRecords.isEmpty()) {
return "当前无玩家挖掘记录";
}
// 拼接每个玩家的统计信息
return playerMiningRecords.entrySet().stream()
.map(entry -> {
UUID uuid = entry.getKey();
List<MiningRecord> records = entry.getValue();
Player player = Bukkit.getPlayer(uuid);
String playerName = (player != null) ? player.getName() : "离线玩家(" + uuid.toString().substring(0, 8) + ")";
// 统计该玩家1分钟和10分钟内的矿石数
int count1Min = countOresInWindow(records, WINDOW_1MIN);
int count10Min = countOresInWindow(records, WINDOW_10MIN);
return String.format("- %s1分钟内%d个10分钟内%d个总记录数%d",
playerName, count1Min, count10Min, records.size());
})
.collect(Collectors.joining("\n"));
}
}

View File

@@ -0,0 +1,117 @@
package org.xgqy.survival.event;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.xgqy.survival.Survival;
import java.util.HashMap;
import java.util.Map;
public class ChatEvent implements Listener {
private static final String ADMIN_OBJECTIVE = "administrator";
private static final int MAX_MESSAGE_LENGTH = 50;
private static final long SHORT_INTERVAL = 1000;
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 Map<Player, Long> lastChatTime = new HashMap<>();
private Map<Player, String> lastMessage = new HashMap<>();
private Map<Player, Long> muteEndTime = new HashMap<>();
private Survival plugin;
public ChatEvent(Survival plugin) {
this.plugin = plugin;
// 启动定时任务每1秒检查一次
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);
lastMessage.remove(player);
}
}
}
}.runTaskTimer(this.plugin, 0L, 20L); // 20L表示每1秒执行一次
}
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
// 检查玩家是否处于禁言状态
if (muteEndTime.containsKey(player) && System.currentTimeMillis() < muteEndTime.get(player)) {
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
player.sendMessage(
ChatColor.RED + "禁言 |" + ChatColor.WHITE + " " +
ChatColor.GRAY + "请不要重复发送相同消息 (你需要等待: " +
ChatColor.DARK_RED + remainingSeconds + "s" +
ChatColor.GRAY + "才能发言)"
);
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;
}
// 仅非管理员受消息长度限制(管理员忽略此检查)
String message = event.getMessage();
if (!isAdmin && message.length() > MAX_MESSAGE_LENGTH) {
event.setCancelled(true);
player.sendMessage(
ChatColor.AQUA + "无法发言 |" + ChatColor.WHITE + ": " +
ChatColor.RED + "消息过长(" +
ChatColor.YELLOW + "非管理员仅可发送 30 字符以内的消息(String.message.length=" + message.length() + ")" +
ChatColor.RED + ")"
);
return;
}
// 检查发言间隔和发言内容
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 + "请不要发送相同的消息,你需要等待一会儿才能继续发言"
);
event.setCancelled(true);
return;
}
}
lastChatTime.put(player, currentTime);
lastMessage.put(player, message);
}
String formattedMessage = player.getPlayerListName() + ":" + message;
event.setCancelled(true);
player.getServer().broadcastMessage(formattedMessage);
}
}

View File

@@ -0,0 +1,238 @@
package org.xgqy.survival.event;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.xgqy.survival.PlayerTags;
import org.xgqy.survival.Survival;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChooseTagEvent implements Listener {
private final Survival plugin;
public ChooseTagEvent(Survival plugin) {
this.plugin = plugin;
}
public static int getint(String str) {
// 正则表达式匹配1个或多个连续数字
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(str);
// 查找第一个匹配的数字序列
if (matcher.find()) {
String numberStr = matcher.group(); // 获取匹配的数字字符串
return Integer.parseInt(numberStr); // 转换为int
} else {
// 没有找到数字时抛出异常
throw new IllegalArgumentException("字符串中未包含数字: " + str);
}
}
@EventHandler
public void onTitleInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (!event.getView().getTitle().contains("称号") && !event.getView().getTitle().contains("商城")) {
return;
}
event.setCancelled(true);
if (event.getView().getTitle().contains("称号")) {
ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() != Material.NAME_TAG) {
return;
}
// -------------------------- 修复:逐步排查边界条件 --------------------------
ItemMeta itemMeta = clickedItem.getItemMeta();
if (itemMeta == null || !itemMeta.hasLore()) {
player.sendMessage(ChatColor.RED + "称号物品数据异常,请联系管理员!");
player.closeInventory();
return;
}
List<String> lore = itemMeta.getLore();
if (lore.isEmpty()) {
player.sendMessage(ChatColor.RED + "称号物品缺少必要信息!");
player.closeInventory();
return;
}
// 1. 剥离颜色+二次trim确保纯文本匹配
String targetTag = lore.getFirst();
// 2. 获取玩家有效称号(自动清理过期)
List<String> playerTags = plugin.getPlayerTags().getTags(player);
// 3. 修复:无有效称号提示
if (playerTags.isEmpty()) {
player.sendMessage(ChatColor.RED + "你暂无有效称号,无法设置!");
player.closeInventory();
return;
}
// 4. 调试日志:打印关键变量(定位问题用)
System.out.println("玩家 " + player.getName() + " 有效称号:" + playerTags);
System.out.println("目标称号(纯文本):" + targetTag);
// 5. 修复:称号未匹配到(索引-1
int targetTagIndex = playerTags.indexOf(targetTag);
if (targetTagIndex == -1) {
player.sendMessage(ChatColor.RED + "称号匹配失败,请联系管理员!");
player.closeInventory();
return;
}
// 6. 检查是否已选中当前称号
int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player);
String currentTag = (currentTagIndex != -1 && currentTagIndex < playerTags.size())
? playerTags.get(currentTagIndex)
: null;
if (targetTag.equals(currentTag)) {
player.sendMessage(ChatColor.RED + "设置失败:已当前使用该称号!");
player.closeInventory();
return;
}
// 7. 最终设置称号
boolean setSuccess = plugin.getPlayerTags().setSelectedTag(player, targetTagIndex);
if (setSuccess) {
player.sendMessage(ChatColor.GREEN + "称号「" + targetTag + ChatColor.GREEN + "」设置成功!");
// 优化:颜色代码闭合(避免后续文本变色)
player.setDisplayName(ChatColor.RESET + "[" + targetTag + "]" + ChatColor.WHITE + player.getName());
player.setPlayerListName(ChatColor.RESET + "[" + targetTag + "]" + ChatColor.WHITE + player.getName());
} else {
// 修复:明确失败原因
player.sendMessage(ChatColor.RED + "称号设置失败!原因:" +
(playerTags.isEmpty() ? "无有效称号" : "索引无效"));
}
} else {
ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() != Material.NAME_TAG) {
return;
}
// -------------------------- 修复:逐步排查边界条件 --------------------------
ItemMeta itemMeta = clickedItem.getItemMeta();
if (itemMeta == null || !itemMeta.hasLore()) {
player.sendMessage(ChatColor.RED + "称号物品数据异常,请联系管理员!");
player.closeInventory();
return;
}
List<String> lore = itemMeta.getLore();
if (lore.isEmpty()) {
player.sendMessage(ChatColor.RED + "称号物品缺少必要信息!");
player.closeInventory();
return;
}
int dq = player.getScoreboard().getObjective("dq").getScore(player).getScore();
int coin = player.getScoreboard().getObjective("coin").getScore(player).getScore();
int cost = getint(lore.get(1));
if (lore.get(1).contains("点券")) {
if (dq < cost) {
player.sendMessage(ChatColor.RED + "你的点卷不足!(还需: " + (cost - dq) + " 个点卷)");
} else {
if (clickedItem.getType() == Material.NAME_TAG) {
PlayerTags playertags = plugin.getPlayerTags();
String gtlore = lore.get(0);
if (gtlore.contains("自定义")) {
player.sendMessage(ChatColor.GREEN + "请在下方输入自定义的称号(&4 = 红色, &e = 黄色, &7 = 白色, &l = 斜体, &o = 加粗)");
player.getScoreboard();
}
} else if (clickedItem.getType() == Material.IRON_INGOT) {
if (player.getScoreboard().getObjective("ifcs").getScore(player).getScore() == 1) {
player.getScoreboard().getObjective("isallowed").getScore(player).setScore(1);
player.sendMessage(ChatColor.GREEN + "月卡购买成功!");
} else {
player.sendMessage(ChatColor.RED + "购买失败!(未查询到购买)");
}
} else if (clickedItem.getType() == Material.GOLD_INGOT) {
if (player.getScoreboard().getObjective("gfcs").getScore(player).getScore() == 1) {
player.getScoreboard().getObjective("isallowed").getScore(player).setScore(2);
player.sendMessage(ChatColor.GREEN + "月卡购买成功!");
} else {
player.sendMessage(ChatColor.RED + "购买失败!(未查询到购买)");
}
} else if (clickedItem.getType() == Material.DIAMOND) {
} else if (clickedItem.getType() == Material.DIRT) {
} else if (clickedItem.getType() == Material.PAPER) {
player.getScoreboard().getObjective("noban").getScore(player).setScore(player.getScoreboard().getObjective("noban").getScore(player).getScore() + 1);
player.sendMessage(ChatColor.GREEN + "自动解封卡购买成功!");
} else if (clickedItem.getType() == Material.RED_STAINED_GLASS_PANE) {
player.closeInventory();
} else {
}
}
} else if (lore.get(1).contains("金币")) {
if (coin < cost) {
player.sendMessage(ChatColor.RED + "你的金币不足!(还需: " + (cost - coin) + " 个金币)");
} else {
if (clickedItem.getType() == Material.NAME_TAG) {
} else if (clickedItem.getType() == Material.DIAMOND) {
} else if (clickedItem.getType() == Material.RED_STAINED_GLASS_PANE) {
} else if (clickedItem.getType() == Material.BARRIER) {
} else if (clickedItem.getType() == Material.WOODEN_PICKAXE) {
ItemStack ds = new ItemStack(Material.WOODEN_PICKAXE);
ds.addEnchantment(Enchantment.EFFICIENCY, 20);
player.sendMessage(ChatColor.GREEN + "已发放至背包!");
player.getInventory().addItem(ds);
coin -= cost;
} else if (clickedItem.getType() == Material.IRON_PICKAXE) {
ItemStack ds = new ItemStack(Material.IRON_PICKAXE);
ds.addEnchantment(Enchantment.EFFICIENCY, 15);
player.getInventory().addItem(ds);
player.sendMessage(ChatColor.GREEN + "已发放至背包!");
coin -= cost;
} else if (clickedItem.getType() == Material.RED_STAINED_GLASS_PANE) {
player.closeInventory();
} else if (clickedItem.getType() == Material.DIAMOND_SWORD) {
ItemStack ds = new ItemStack(Material.DIAMOND_SWORD);
ds.addEnchantment(Enchantment.SHARPNESS, 10);
player.getInventory().addItem(ds);
player.sendMessage(ChatColor.GREEN + "已发放至背包!");
coin -= cost;
} else {
player.sendMessage(ChatColor.RED + "购买失败:未知的物品");
}
}
} else {
player.sendMessage("未知的货币!");
}
player.getScoreboard().getObjective("dq").getScore(player).setScore(dq);
player.getScoreboard().getObjective("coin").getScore(player).setScore(coin);
}
player.closeInventory();
}
@EventHandler
public void onTitleInventoryDrag(InventoryDragEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (event.getView().getTitle().contains("称号")) {
event.setCancelled(true);
//player.sendMessage(ChatColor.RED + "称号界面不允许拖拽物品!");
player.closeInventory();
}
}
}

View File

@@ -0,0 +1,35 @@
package org.xgqy.survival.event;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.xgqy.survival.PlayerTags;
import org.xgqy.survival.Survival;
import java.util.List;
public class JoinEvent implements Listener {
private Survival plugin;
public JoinEvent(Survival plugin) {
this.plugin = plugin;
}
@EventHandler
private void join(PlayerJoinEvent e) {
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 进行切换");
}
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);
}
}

View File

@@ -0,0 +1,51 @@
name: survival
version: '1.0'
main: org.xgqy.survival.Survival
api-version: '1.21'
authors: [ Chen yuchen_1 ]
description: A plugin to make bedwars in spigot
commands:
pvp:
description: to open/close the PVP
usage: /<command>
settag:
description: to set player's tags
usage: /<command> <Player:player> <add|remove> <Tag:tag> <Color:color>
permission: permission.settag
permission-message: 你没有权限执行此命令!
report:
description: Report players
usage: /<command> <Player:player> <String:Reason>
handle:
description: handle players reported
usage: /<command> <Player:player>
permission: permission.handle
permission-message: 无法执行该命令,请确认拥有管理员权限
tag:
description: to set tag
usage: /<command>
hub:
description: get to the main lobby
usage: /<command>
teleport:
description: teleport to a player
usage: /<command> <Player:player>
tpacc:
description: teleport to a player
usage: /<command> <Player:player>
tpfin:
description: teleport to a player
usage: /<command> <Player:player>
help:
description: see the help
usage: /<command>
shop:
description: to open the shop
usage: /<command>
permissions:
permission.settag:
description: Allows setting player tags
default: op
permission.handle:
description: handle players reported
default: op