commit 2944f192db10a7bf72b24f3a8911386272fb6401 Author: Fortern Date: Sun Oct 12 17:10:23 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a03b851 --- /dev/null +++ b/.gitignore @@ -0,0 +1,116 @@ +# User-specific stuff +.vscode +*.iml +*.ipr +*.iws + +# IntelliJ +out/ +.idea + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run*/ + +# Gradle +.gradle/ +build/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..191a812 --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + org.xgqy + survival + 1.0 + jar + + survival + + + 21 + UTF-8 + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + + org.spigotmc + spigot-api + 1.21.1-R0.1-SNAPSHOT + provided + + + org.jetbrains + annotations + 26.0.2 + provided + + + diff --git a/src/main/java/org/xgqy/survival/PlayerTags.java b/src/main/java/org/xgqy/survival/PlayerTags.java new file mode 100644 index 0000000..0edd009 --- /dev/null +++ b/src/main/java/org/xgqy/survival/PlayerTags.java @@ -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> playerTags = new HashMap<>(); + // 新增:玩家-(称号-到期时间戳)映射(毫秒级,永久用Long.MAX_VALUE) + private final Map> expireTimeMap = new HashMap<>(); + // 原有:玩家-选中称号索引映射 + public Map 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 validTags = new ArrayList<>(); + List allTags = playerTags.getOrDefault(uuid, new ArrayList<>()); + Map 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 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 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)还是新版本(含过期时间) + 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> oldPlayerTags = (Map>) firstObj; + Map oldSelectTag = (Map) secondObj; + + // 旧数据转换:为每个称号设置“当前时间+7天”的到期时间 + for (Map.Entry> entry : oldPlayerTags.entrySet()) { + UUID uuid = entry.getKey(); + List oldTags = entry.getValue(); + if (oldTags == null || oldTags.isEmpty()) { + continue; + } + + // 为当前玩家创建“称号-到期时间”映射 + Map 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>) firstObj); + playerselectTag.putAll((Map) secondObj); + expireTimeMap.putAll((Map>) 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 tags = new ArrayList<>(); + Map 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 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 tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>()); + Map 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 tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>()); + Map 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 tags = playerTags.get(uuid); + Map 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 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 tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>()); + List 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分钟"; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/Survival.java b/src/main/java/org/xgqy/survival/Survival.java new file mode 100644 index 0000000..fc07306 --- /dev/null +++ b/src/main/java/org/xgqy/survival/Survival.java @@ -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 krt = new HashMap<>(); + public Map banlist = new HashMap<>(); + public Map banreason = new HashMap<>(); + public Map> reportlist = new HashMap<>(); + public Map teleport = new HashMap<>(); + public Map Ateleport = new HashMap<>(); + public Map teleportp = new HashMap<>(); + public Map isteleport = new HashMap<>(); + public Map 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; + } +} diff --git a/src/main/java/org/xgqy/survival/command/DqshopCommandExecutor.java b/src/main/java/org/xgqy/survival/command/DqshopCommandExecutor.java new file mode 100644 index 0000000..2c1ae6e --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/DqshopCommandExecutor.java @@ -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 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 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 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 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 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 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/HandleCommandExecutor.java b/src/main/java/org/xgqy/survival/command/HandleCommandExecutor.java new file mode 100644 index 0000000..747f1a0 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/HandleCommandExecutor.java @@ -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 - 处理玩家\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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/HelpCommandExecutor.java b/src/main/java/org/xgqy/survival/command/HelpCommandExecutor.java new file mode 100644 index 0000000..e4d5877 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/HelpCommandExecutor.java @@ -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 - 同意/拒绝玩家传送"); + sender.sendMessage(ChatColor.GREEN + "/tpfin - 结束传送"); + sender.sendMessage(ChatColor.YELLOW + "-----------------------------"); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/HubCommandExecutor.java b/src/main/java/org/xgqy/survival/command/HubCommandExecutor.java new file mode 100644 index 0000000..44d877b --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/HubCommandExecutor.java @@ -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 + "传送状态已同步"); + } + } +} diff --git a/src/main/java/org/xgqy/survival/command/PvpCommandExecutor.java b/src/main/java/org/xgqy/survival/command/PvpCommandExecutor.java new file mode 100644 index 0000000..ba174ab --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/PvpCommandExecutor.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/ReportCommandExecutor.java b/src/main/java/org/xgqy/survival/command/ReportCommandExecutor.java new file mode 100644 index 0000000..985405c --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/ReportCommandExecutor.java @@ -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 playerHitCount = new HashMap<>(); + private final Map dummyTargetMap = new HashMap<>(); + private final Map 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 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 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 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() + + " 因多次攻击举报假人已被自动封禁"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/SetTagCommandExecutor.java b/src/main/java/org/xgqy/survival/command/SetTagCommandExecutor.java new file mode 100644 index 0000000..5977465 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/SetTagCommandExecutor.java @@ -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 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 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 + " - 示例1(30天):/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"); + } +} diff --git a/src/main/java/org/xgqy/survival/command/TagCommandExecutor.java b/src/main/java/org/xgqy/survival/command/TagCommandExecutor.java new file mode 100644 index 0000000..b501005 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/TagCommandExecutor.java @@ -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 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 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. 制作分隔用的黑色玻璃Pane(9-17号槽位,一行分隔) + ItemStack blackPane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE); + ItemMeta blackPaneMeta = blackPane.getItemMeta(); // 修复原bug:用自己的ItemMeta,而非其他物品的 + List 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/TeleportCommandExecutor.java b/src/main/java/org/xgqy/survival/command/TeleportCommandExecutor.java new file mode 100644 index 0000000..180601e --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/TeleportCommandExecutor.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/TpAccCommandExecutor.java b/src/main/java/org/xgqy/survival/command/TpAccCommandExecutor.java new file mode 100644 index 0000000..44ff0fd --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/TpAccCommandExecutor.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/TpFinCommandExecutor.java b/src/main/java/org/xgqy/survival/command/TpFinCommandExecutor.java new file mode 100644 index 0000000..859c3c5 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/TpFinCommandExecutor.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/AntiXray.java b/src/main/java/org/xgqy/survival/event/AntiXray.java new file mode 100644 index 0000000..86ad7f1 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/AntiXray.java @@ -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 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 IGNORED_GAMEMODES = new HashSet<>(Arrays.asList( + "CREATIVE", + "SPECTATOR" + )); + + // 4. 管理员权限节点(只有拥有该权限的玩家会收到预警) + private static final String ADMIN_PERMISSION = "survival.admin.antixray"; + + // 存储玩家挖掘记录:key=玩家UUID,value=该玩家的所有稀有矿石挖掘记录 + private final Map> 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 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 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] 疑似透视行为 - 玩家:%s(UUID:%s),1分钟内矿石数:%d(阈值:%d),10分钟内矿石数:%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 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("- %s:1分钟内%d个,10分钟内%d个(总记录数:%d)", + playerName, count1Min, count10Min, records.size()); + }) + .collect(Collectors.joining("\n")); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/ChatEvent.java b/src/main/java/org/xgqy/survival/event/ChatEvent.java new file mode 100644 index 0000000..00ecc91 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/ChatEvent.java @@ -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 lastChatTime = new HashMap<>(); + private Map lastMessage = new HashMap<>(); + private Map 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 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); + } +} diff --git a/src/main/java/org/xgqy/survival/event/ChooseTagEvent.java b/src/main/java/org/xgqy/survival/event/ChooseTagEvent.java new file mode 100644 index 0000000..a320d38 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/ChooseTagEvent.java @@ -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 lore = itemMeta.getLore(); + if (lore.isEmpty()) { + player.sendMessage(ChatColor.RED + "称号物品缺少必要信息!"); + player.closeInventory(); + return; + } + + // 1. 剥离颜色+二次trim,确保纯文本匹配 + String targetTag = lore.getFirst(); + // 2. 获取玩家有效称号(自动清理过期) + List 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 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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/JoinEvent.java b/src/main/java/org/xgqy/survival/event/JoinEvent.java new file mode 100644 index 0000000..1761c06 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/JoinEvent.java @@ -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 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); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..1d1a0f1 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -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: / + settag: + description: to set player's tags + usage: / + permission: permission.settag + permission-message: 你没有权限执行此命令! + report: + description: Report players + usage: / + handle: + description: handle players reported + usage: / + permission: permission.handle + permission-message: 无法执行该命令,请确认拥有管理员权限 + tag: + description: to set tag + usage: / + hub: + description: get to the main lobby + usage: / + teleport: + description: teleport to a player + usage: / + tpacc: + description: teleport to a player + usage: / + tpfin: + description: teleport to a player + usage: / + help: + description: see the help + usage: / + shop: + description: to open the shop + usage: / +permissions: + permission.settag: + description: Allows setting player tags + default: op + permission.handle: + description: handle players reported + default: op \ No newline at end of file