4907 lines
188 KiB
Plaintext
4907 lines
188 KiB
Plaintext
package Sdf1_login;
|
||
|
||
import org.bukkit.*;
|
||
import org.bukkit.block.Block;
|
||
import org.bukkit.command.CommandSender;
|
||
import org.bukkit.entity.Player;
|
||
import org.bukkit.event.EventHandler;
|
||
import org.bukkit.event.Listener;
|
||
import org.bukkit.event.block.Action;
|
||
import org.bukkit.event.block.BlockBreakEvent;
|
||
import org.bukkit.event.block.BlockPlaceEvent;
|
||
import org.bukkit.event.entity.*;
|
||
import org.bukkit.event.player.*;
|
||
import org.bukkit.inventory.ItemStack;
|
||
import org.bukkit.inventory.meta.ItemMeta;
|
||
import org.bukkit.potion.PotionEffect;
|
||
import org.bukkit.potion.PotionEffectType;
|
||
import org.bukkit.event.block.BlockBurnEvent;
|
||
import org.bukkit.event.block.BlockIgniteEvent;
|
||
import org.bukkit.event.block.BlockSpreadEvent;
|
||
import org.bukkit.entity.Entity;
|
||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||
import java.sql.*;
|
||
import org.bukkit.event.entity.*;
|
||
import org.bukkit.event.player.*;
|
||
import org.bukkit.GameMode;
|
||
import org.bukkit.event.EventPriority;
|
||
|
||
|
||
import java.io.*;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.*;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import org.bukkit.Bukkit;
|
||
import java.util.Iterator;
|
||
import java.util.Set;
|
||
import java.util.HashSet;
|
||
import org.bukkit.scheduler.BukkitRunnable;
|
||
import org.bukkit.scheduler.BukkitTask;
|
||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||
import org.bukkit.entity.Item;
|
||
|
||
|
||
|
||
|
||
public class AreaProtection implements Listener {
|
||
|
||
private final Main plugin;
|
||
public List<String[]> giveEffects = new ArrayList<>();
|
||
private final Map<UUID, Set<String>> playerAreas
|
||
= new HashMap<>();
|
||
private final Map<String, AreaConfig> areas =
|
||
new ConcurrentHashMap<>();
|
||
private Connection dbConnection;
|
||
|
||
// 全局白名单(玩家)
|
||
private final Set<String> globalPlayerWhitelist =
|
||
ConcurrentHashMap.newKeySet();
|
||
// 区域白名单 key=区域名 value=玩家名集合
|
||
private final Map<String, Set<String>> areaPlayerWhitelist =
|
||
new ConcurrentHashMap<>();
|
||
// 全局黑名单(物品ID)
|
||
private final Set<String> globalItemBlacklist =
|
||
ConcurrentHashMap.newKeySet();
|
||
// 区域黑名单
|
||
private final Map<String, Set<String>> areaItemBlacklist =
|
||
new ConcurrentHashMap<>();
|
||
|
||
// 选地
|
||
private static final Material WAND = Material.BREEZE_ROD;
|
||
|
||
private final Map<UUID, Location> pos1 =
|
||
new ConcurrentHashMap<>();
|
||
private final Map<UUID, Location> pos2 =
|
||
new ConcurrentHashMap<>();
|
||
private final Set<UUID> selecting =
|
||
ConcurrentHashMap.newKeySet();
|
||
public boolean denyRaid = false;
|
||
// ===== 统一文件夹 =====
|
||
private final File rootDir;
|
||
private final File whitelistDir;
|
||
private final File globalWhitelistFile;
|
||
private final File oldDbFile;
|
||
private final File newDbFile;
|
||
|
||
// 边框显示
|
||
private final Map<UUID, Integer> displayTaskIds =
|
||
new ConcurrentHashMap<>();
|
||
private final Map<String, Set<String>> globalWhitelist
|
||
= new HashMap<>();
|
||
private final Map<String, Set<String>> areaWhitelist
|
||
= new HashMap<>();
|
||
private final Map<UUID, Long> protectedEntities
|
||
= new ConcurrentHashMap<>();
|
||
// 区域给予的效果记录(贴标)
|
||
private final Map<UUID, List<PotionEffectType>> playerAppliedEffects = new HashMap<>();
|
||
// 延时清理任务
|
||
private static final long MOVE_DEBOUNCE_MS = 100;
|
||
// 新:全部改为线程安全
|
||
private final Map<UUID, String> playerCurrentArea
|
||
= new ConcurrentHashMap<>();
|
||
private final Map<UUID, List<PotionEffectType>> playerMarkedEffects
|
||
= new ConcurrentHashMap<>();
|
||
private final Map<UUID, BukkitTask> pendingClearTask
|
||
= new ConcurrentHashMap<>();
|
||
private final Map<UUID, String> pendingClearArea
|
||
= new ConcurrentHashMap<>();
|
||
private final Map<UUID, Long> lastMoveProcess
|
||
= new ConcurrentHashMap<>();
|
||
// 防止传送时重复处理
|
||
private final Set<UUID> teleporting
|
||
= ConcurrentHashMap.newKeySet();
|
||
// 待清理的效果列表(与延时任务配合)
|
||
private final Map<UUID, List<PotionEffectType>> pendingClearEffects
|
||
= new ConcurrentHashMap<>();
|
||
|
||
|
||
|
||
// ==================== 强制游戏模式 ====================
|
||
|
||
/**
|
||
* 监听玩家尝试切换游戏模式
|
||
* 如果在强制游戏模式区域且不在豁免名单中,则取消切换
|
||
*/
|
||
// 防止递归的标记
|
||
private final Set<UUID> modeSwitching
|
||
= ConcurrentHashMap.newKeySet();
|
||
@EventHandler(priority = EventPriority.HIGHEST)
|
||
public void onGameModeChange(
|
||
PlayerGameModeChangeEvent event) {
|
||
if (event.isCancelled()) return;
|
||
|
||
Player p = event.getPlayer();
|
||
UUID uid = p.getUniqueId();
|
||
|
||
if (modeSwitching.contains(uid)) return;
|
||
|
||
String areaName = playerCurrentArea.get(uid);
|
||
if (areaName == null) return;
|
||
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac == null) return;
|
||
if (ac.enforceGameMode == null
|
||
|| ac.enforceGameMode.isEmpty()) return;
|
||
if (isExemptFromGameMode(p, areaName)) return;
|
||
|
||
event.setCancelled(true);
|
||
|
||
modeSwitching.add(uid);
|
||
try {
|
||
GameMode target = GameMode.valueOf(
|
||
ac.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
}
|
||
} finally {
|
||
modeSwitching.remove(uid);
|
||
}
|
||
|
||
// ★ 切换后发一次中文提示
|
||
p.sendMessage(formatAreaMsg(
|
||
"&c该区域强制 " + ac.enforceGameMode
|
||
+ " 模式,无法切换"));
|
||
}
|
||
|
||
public Set<UUID> getAlreadyForced() {
|
||
return alreadyForced;
|
||
}
|
||
|
||
|
||
// ==================== 定时强制游戏模式 ====================
|
||
|
||
private BukkitTask enforceTask;
|
||
|
||
/**
|
||
* 启动定时强制任务
|
||
* 每秒检查一次所有在线玩家
|
||
*/
|
||
private BukkitTask enforceTask1;
|
||
|
||
// 记录上次强制模式的玩家(避免重复发消息)
|
||
private final Set<UUID> alreadyForced
|
||
= ConcurrentHashMap.newKeySet();
|
||
|
||
public void startEnforceTask() {
|
||
if (enforceTask != null) {
|
||
enforceTask.cancel();
|
||
}
|
||
enforceTask = new BukkitRunnable() {
|
||
@Override
|
||
public void run() {
|
||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||
UUID uid = p.getUniqueId();
|
||
String areaName =
|
||
playerCurrentArea.get(uid);
|
||
if (areaName == null) {
|
||
// 不在任何区域,清除标记
|
||
alreadyForced.remove(uid);
|
||
continue;
|
||
}
|
||
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac == null) continue;
|
||
if (ac.enforceGameMode == null
|
||
|| ac.enforceGameMode
|
||
.isEmpty()) continue;
|
||
if (isExemptFromGameMode(
|
||
p, areaName)) {
|
||
alreadyForced.remove(uid);
|
||
continue;
|
||
}
|
||
|
||
GameMode target =
|
||
GameMode.valueOf(
|
||
ac.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
modeSwitching.add(uid);
|
||
try {
|
||
p.setGameMode(target);
|
||
} finally {
|
||
modeSwitching.remove(uid);
|
||
}
|
||
|
||
// ★ 只在玩家已被强制过时才发消息
|
||
// 首次不发,后续才提醒
|
||
if (alreadyForced.contains(uid)) {
|
||
p.sendMessage(formatAreaMsg(
|
||
"&c该区域强制 "
|
||
+ ac.enforceGameMode
|
||
+ " 模式,无法切换"));
|
||
}
|
||
alreadyForced.add(uid);
|
||
} else {
|
||
// 模式一致,清除标记
|
||
alreadyForced.remove(uid);
|
||
}
|
||
}
|
||
}
|
||
}.runTaskTimer(plugin, 20L, 20L);
|
||
}
|
||
|
||
public void stopEnforceTask() {
|
||
if (enforceTask != null) {
|
||
enforceTask.cancel();
|
||
enforceTask = null;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 检查玩家是否豁免于游戏模式强制
|
||
* 豁免条件:全局白名单 / 区域白名单 / 模式排除名单
|
||
*/
|
||
private boolean isExemptFromGameMode(
|
||
Player p, String areaName) {
|
||
String name = p.getName();
|
||
|
||
/* if (p.isOp()) {
|
||
plugin.getLogger().info(
|
||
"[防护-调试] 豁免原因: OP, 玩家=" + name);
|
||
return true;
|
||
}*/
|
||
if (globalPlayerWhitelist.contains(name)) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] 豁免原因: 全局白名单, 玩家="
|
||
+ name);*/
|
||
return true;
|
||
}
|
||
Set<String> aw =
|
||
areaPlayerWhitelist.get(areaName);
|
||
if (aw != null && aw.contains(name)) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] 豁免原因: 区域白名单, 玩家="
|
||
+ name + " 区域=" + areaName);*/
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac != null && ac.modeExempt.contains(name)) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] 豁免原因: 模式排除名单, 玩家="
|
||
+ name);*/
|
||
return true;
|
||
}
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] 无豁免, 玩家=" + name);*/
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 玩家进入区域时,如果该区域强制游戏模式,
|
||
* 则立即设置(排除豁免玩家)
|
||
*/
|
||
private void applyEnforcedGameMode(Player p,
|
||
String areaName) {
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac == null) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] applyMode: ac=null, area="
|
||
+ areaName);*/
|
||
return;
|
||
}
|
||
if (ac.enforceGameMode == null
|
||
|| ac.enforceGameMode.isEmpty()) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] applyMode: 未配置强制模式, area="
|
||
+ areaName);*/
|
||
return;
|
||
}
|
||
|
||
boolean exempt = isExemptFromGameMode(
|
||
p, areaName);
|
||
/* plugin.getLogger().info(
|
||
"[防护-调试] applyMode: 玩家=" + p.getName()
|
||
+ " 区域=" + areaName
|
||
+ " 强制=" + ac.enforceGameMode
|
||
+ " 当前=" + p.getGameMode()
|
||
+ " 豁免=" + exempt
|
||
+ " OP=" + p.isOp()
|
||
+ " 全白=" + globalPlayerWhitelist
|
||
.contains(p.getName()));*/
|
||
|
||
if (exempt) return;
|
||
|
||
try {
|
||
GameMode target = GameMode.valueOf(
|
||
ac.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
/* plugin.getLogger().info(
|
||
"[防护] 强制切换 " + p.getName()
|
||
+ " → " + ac.enforceGameMode);*/
|
||
}
|
||
} catch (Exception e) {
|
||
/* plugin.getLogger().warning(
|
||
"[防护] 强制模式失败: "
|
||
+ e.getMessage());*/
|
||
}
|
||
}
|
||
|
||
public AreaProtection(Main plugin) {
|
||
this.plugin = plugin;
|
||
|
||
// 统一根目录
|
||
rootDir = new File(plugin.getDataFolder(), "区域防护");
|
||
if (!rootDir.exists()) rootDir.mkdirs();
|
||
|
||
// 白名单子目录
|
||
whitelistDir = new File(rootDir, "whitelists");
|
||
if (!whitelistDir.exists()) whitelistDir.mkdirs();
|
||
globalWhitelistFile = new File(
|
||
whitelistDir, "全局白名单.txt");
|
||
|
||
// 数据库迁移
|
||
oldDbFile = new File(
|
||
plugin.getDataFolder(), "area_effects.db");
|
||
newDbFile = new File(rootDir, "area_effects.db");
|
||
migrateDatabase();
|
||
|
||
// 初始化
|
||
initDatabase();
|
||
writeDefaultConfig();
|
||
loadAllAreas();
|
||
loadWhitelists();
|
||
recoverPendingEffects();
|
||
}
|
||
public void recoverPendingEffects() {
|
||
try {
|
||
// 1. 遍历所有在线玩家(而不是遍历DB记录)
|
||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||
UUID uid = p.getUniqueId();
|
||
String playerName = p.getName();
|
||
|
||
// 2. 获取玩家当前所在区域
|
||
AreaConfig currentAc = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
|
||
// 3. 获取该玩家在DB中的所有历史记录
|
||
List<String> recordedAreas =
|
||
getPlayerAreas(uid);
|
||
|
||
// 4. 清理不在当前区域的旧记录
|
||
for (String recordedArea : recordedAreas) {
|
||
if (currentAc == null
|
||
|| !currentAc.name
|
||
.equals(recordedArea)) {
|
||
// 玩家不在这个区域了 → 清DB
|
||
removePlayerEffects(
|
||
uid, recordedArea);
|
||
plugin.getLogger().info(
|
||
"[防护] 重连: " + playerName
|
||
+ " 不在" + recordedArea
|
||
+ ", 清理旧记录");
|
||
}
|
||
}
|
||
|
||
// 5. 如果在区域中 → 恢复/给予效果
|
||
if (currentAc != null) {
|
||
plugin.getLogger().info(
|
||
"[防护] 重连: " + playerName
|
||
+ " 在" + currentAc.name
|
||
+ ", 恢复效果");
|
||
applyRegionEffects(p, uid,
|
||
currentAc.name, currentAc);
|
||
} else {
|
||
plugin.getLogger().info(
|
||
"[防护] 重连: " + playerName
|
||
+ " 不在任何区域, 跳过");
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 恢复效果失败: "
|
||
+ e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onQuit(PlayerQuitEvent event) {
|
||
Player p = event.getPlayer();
|
||
UUID uid = p.getUniqueId();
|
||
|
||
// 取消延时清理任务
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
pendingClearArea.remove(uid);
|
||
pendingClearEffects.remove(uid);
|
||
}
|
||
|
||
// 清标记
|
||
playerMarkedEffects.remove(uid);
|
||
playerCurrentArea.remove(uid);
|
||
}
|
||
|
||
|
||
// ==================== 数据库迁移 ====================
|
||
|
||
/**
|
||
* 如果旧位置有 db 文件,移到新位置
|
||
* 如果新位置已有,不动
|
||
* 如果旧位置也没有,在新位置创建
|
||
*/
|
||
|
||
private void migrateDatabase() {
|
||
if (newDbFile.exists()) {
|
||
if (oldDbFile.exists()
|
||
&& !oldDbFile.equals(newDbFile)) {
|
||
oldDbFile.delete();
|
||
}
|
||
return;
|
||
}
|
||
if (oldDbFile.exists()
|
||
&& !oldDbFile.equals(newDbFile)) {
|
||
try {
|
||
java.nio.file.Files.move(
|
||
oldDbFile.toPath(),
|
||
newDbFile.toPath(),
|
||
java.nio.file.StandardCopyOption
|
||
.REPLACE_EXISTING);
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 迁移数据库失败: "
|
||
+ e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 保存玩家进入区域时给予的效果
|
||
private void savePlayerEffects(UUID uid, String playerName,
|
||
String areaName, List<PotionEffectType> effects) {
|
||
try {
|
||
// 先删除该区域旧记录,避免重复
|
||
PreparedStatement del = dbConnection.prepareStatement(
|
||
"DELETE FROM player_effects "
|
||
+ "WHERE uuid = ? AND area_name = ?");
|
||
del.setString(1, uid.toString());
|
||
del.setString(2, areaName);
|
||
del.executeUpdate();
|
||
del.close();
|
||
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"INSERT INTO player_effects "
|
||
+ "(uuid, player_name, area_name, "
|
||
+ "effect_type, effect_level, "
|
||
+ "effect_duration, enter_time) "
|
||
+ "VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||
long now = System.currentTimeMillis();
|
||
for (PotionEffectType type : effects) {
|
||
ps.setString(1, uid.toString());
|
||
ps.setString(2, playerName);
|
||
ps.setString(3, areaName);
|
||
ps.setString(4, type.getName());
|
||
ps.setInt(5, 0);
|
||
ps.setInt(6, 999);
|
||
ps.setLong(7, now);
|
||
ps.addBatch();
|
||
}
|
||
ps.executeBatch();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 保存效果失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// 查询玩家在某个区域获得的效果
|
||
private List<PotionEffectType> getPlayerEffects(
|
||
UUID uid, String areaName) {
|
||
List<PotionEffectType> list = new ArrayList<>();
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"SELECT effect_type FROM player_effects "
|
||
+ "WHERE uuid = ? AND area_name = ?");
|
||
ps.setString(1, uid.toString());
|
||
ps.setString(2, areaName);
|
||
ResultSet rs = ps.executeQuery();
|
||
while (rs.next()) {
|
||
PotionEffectType type = PotionEffectType
|
||
.getByName(rs.getString("effect_type"));
|
||
if (type != null) list.add(type);
|
||
}
|
||
rs.close();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 查询效果失败: " + e.getMessage());
|
||
}
|
||
return list;
|
||
}
|
||
|
||
// 删除玩家在某个区域的效果记录
|
||
private void removePlayerEffects(UUID uid, String areaName) {
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"DELETE FROM player_effects "
|
||
+ "WHERE uuid = ? AND area_name = ?");
|
||
ps.setString(1, uid.toString());
|
||
ps.setString(2, areaName);
|
||
ps.executeUpdate();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 删除效果失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// 删除玩家所有效果记录
|
||
private void removeAllPlayerEffects(UUID uid) {
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"DELETE FROM player_effects "
|
||
+ "WHERE uuid = ?");
|
||
ps.setString(1, uid.toString());
|
||
ps.executeUpdate();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 删除全部效果失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// 获取玩家所有记录的区域
|
||
private List<String> getPlayerAreas(UUID uid) {
|
||
List<String> list = new ArrayList<>();
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"SELECT DISTINCT area_name "
|
||
+ "FROM player_effects WHERE uuid = ?");
|
||
ps.setString(1, uid.toString());
|
||
ResultSet rs = ps.executeQuery();
|
||
while (rs.next()) {
|
||
list.add(rs.getString("area_name"));
|
||
}
|
||
rs.close();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 查询区域失败: " + e.getMessage());
|
||
}
|
||
return list;
|
||
}
|
||
|
||
// 查询所有待处理的效果(跨服重启也能恢复)
|
||
private List<String[]> getAllPendingEffects() {
|
||
List<String[]> list = new ArrayList<>();
|
||
try {
|
||
Statement stmt = dbConnection.createStatement();
|
||
ResultSet rs = stmt.executeQuery(
|
||
"SELECT uuid, player_name, area_name, "
|
||
+ "effect_type FROM player_effects");
|
||
while (rs.next()) {
|
||
list.add(new String[]{
|
||
rs.getString("uuid"),
|
||
rs.getString("player_name"),
|
||
rs.getString("area_name"),
|
||
rs.getString("effect_type")});
|
||
}
|
||
rs.close();
|
||
stmt.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 查询全部效果失败: " + e.getMessage());
|
||
}
|
||
return list;
|
||
}
|
||
|
||
|
||
private void writeDefaultConfig() {
|
||
File f = new File(rootDir, "末地保护区.txt");
|
||
if (f.exists()) return;
|
||
|
||
String endWorld = "world_the_end";
|
||
for (org.bukkit.World w : Bukkit.getWorlds()) {
|
||
if (w.getEnvironment()
|
||
== org.bukkit.World.Environment.THE_END) {
|
||
endWorld = w.getName();
|
||
break;
|
||
}
|
||
}
|
||
|
||
final String ew = endWorld;
|
||
try {
|
||
PrintWriter pw = new PrintWriter(
|
||
new OutputStreamWriter(
|
||
new FileOutputStream(f),
|
||
StandardCharsets.UTF_8));
|
||
pw.println("# 末地保护区");
|
||
pw.println("起点: " + ew + ",-30000000,-30000000");
|
||
pw.println("终点: " + ew + ",30000000,30000000");
|
||
pw.println("高度范围: 0-255");
|
||
pw.println();
|
||
pw.println("没收物品: BROWN_MUSHROOM,RED_MUSHROOM,MUSHROOM_STEW");
|
||
pw.println("没收提示: 你携带了违禁品「蘑菇」,已被全部没收!");
|
||
pw.println("通报批评: {player} 在末地违规携带蘑菇!");
|
||
pw.println();
|
||
pw.println("禁止放置方块");
|
||
pw.println("禁止破坏方块");
|
||
pw.println("禁止PVP");
|
||
pw.println("禁止袭击");
|
||
pw.println();
|
||
pw.println("效果: 夜视 1 999");
|
||
pw.println("进入提示: 末地保护区 - 请遵守规则");
|
||
pw.println("离开提示: 已离开末地保护区");
|
||
pw.close();
|
||
plugin.getLogger().info("[防护] 生成末地配置, 世界: " + ew);
|
||
} catch (IOException ignored) {
|
||
}
|
||
}
|
||
|
||
public void reload() {
|
||
loadAllAreas();
|
||
loadWhitelists();
|
||
// 强制输出醒目的中文日志
|
||
plugin.getLogger().info("========================================");
|
||
plugin.getLogger().info("[防护] 已重载 " + areas.size() + " 个区域");
|
||
plugin.getLogger().info("[防护] 已重载白名单");
|
||
plugin.getLogger().info("========================================");
|
||
}
|
||
|
||
// ==================== 消息格式化 ====================
|
||
|
||
/**
|
||
* 格式化区域消息
|
||
* 1. & → §(颜色符号转换)
|
||
* 2. <br> 和 \n → 换行
|
||
* 3. <b>内容</b> → §l内容§r(加粗)
|
||
* 4. 自动添加 [区域防护] 前缀
|
||
*/
|
||
private String formatAreaMsg(String raw) {
|
||
if (raw == null || raw.isEmpty()) return "";
|
||
|
||
String msg = raw;
|
||
|
||
// & → §
|
||
for (int i = 0; i <= 9; i++) {
|
||
msg = msg.replace("&" + i, "§" + i);
|
||
}
|
||
for (char c : "abcdefklnor".toCharArray()) {
|
||
msg = msg.replace("&" + c, "§" + c);
|
||
}
|
||
|
||
// 换行
|
||
msg = msg.replace("<br>", "\n");
|
||
msg = msg.replace("\\n", "\n");
|
||
|
||
// HTML → Minecraft格式(栈式解析,支持嵌套)
|
||
msg = convertHtmlTags(msg);
|
||
|
||
// 前缀
|
||
msg = "§c§l【区域防护】§r " + msg;
|
||
|
||
return msg;
|
||
}
|
||
|
||
/**
|
||
* 栈式HTML标签解析
|
||
* 处理 <u><b>text</b> rest</u> 这样的嵌套标签
|
||
* 内层关闭时重新应用外层格式
|
||
*/
|
||
private String convertHtmlTags(String msg) {
|
||
StringBuilder sb = new StringBuilder();
|
||
java.util.Deque<Character> stack =
|
||
new ArrayDeque<>();
|
||
int i = 0;
|
||
|
||
while (i < msg.length()) {
|
||
if (msg.charAt(i) == '<'
|
||
&& i + 1 < msg.length()) {
|
||
if (msg.charAt(i + 1) == '/') {
|
||
// 关闭标签 </x>
|
||
int end = msg.indexOf('>', i);
|
||
if (end < 0) {
|
||
sb.append(msg.charAt(i));
|
||
i++;
|
||
continue;
|
||
}
|
||
String tag = msg.substring(i + 2, end)
|
||
.trim().toLowerCase();
|
||
char code = htmlToMc(tag);
|
||
if (code != 0 && !stack.isEmpty()
|
||
&& stack.peek() == code) {
|
||
stack.pop();
|
||
if (!stack.isEmpty()) {
|
||
// 重置后重新应用外层格式
|
||
sb.append("§r");
|
||
for (char c : stack) {
|
||
sb.append("§").append(c);
|
||
}
|
||
} else {
|
||
sb.append("§r");
|
||
}
|
||
}
|
||
i = end + 1;
|
||
} else {
|
||
// 开放标签 <x>
|
||
int end = msg.indexOf('>', i);
|
||
if (end < 0) {
|
||
sb.append(msg.charAt(i));
|
||
i++;
|
||
continue;
|
||
}
|
||
String tag = msg.substring(i + 1, end)
|
||
.trim().toLowerCase();
|
||
char code = htmlToMc(tag);
|
||
if (code != 0) {
|
||
stack.push(code);
|
||
sb.append("§").append(code);
|
||
}
|
||
i = end + 1;
|
||
}
|
||
} else {
|
||
sb.append(msg.charAt(i));
|
||
i++;
|
||
}
|
||
}
|
||
return sb.toString();
|
||
}
|
||
|
||
private char htmlToMc(String tag) {
|
||
switch (tag) {
|
||
case "b": return 'l';
|
||
case "i": return 'o';
|
||
case "u": return 'n';
|
||
case "s": return 'm';
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
// =============== 加载 ====================
|
||
|
||
public void loadAllAreas() {
|
||
areas.clear();
|
||
File[] files = rootDir.listFiles(
|
||
(File d, String n) -> n.endsWith(".txt"));
|
||
if (files == null) return;
|
||
for (File f : files) {
|
||
try {
|
||
String name = f.getName()
|
||
.replace(".txt", "");
|
||
AreaConfig ac = parseArea(name, f);
|
||
areas.put(name, ac);
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 加载失败: " + f.getName());
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
private void initDatabase() {
|
||
try {
|
||
dbConnection = DriverManager.getConnection(
|
||
"jdbc:sqlite:" + newDbFile.getAbsolutePath());
|
||
Statement stmt = dbConnection.createStatement();
|
||
stmt.executeUpdate(
|
||
"CREATE TABLE IF NOT EXISTS player_effects ("
|
||
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||
+ "uuid TEXT NOT NULL,"
|
||
+ "player_name TEXT,"
|
||
+ "area_name TEXT NOT NULL,"
|
||
+ "effect_type TEXT NOT NULL,"
|
||
+ "effect_level INTEGER DEFAULT 0,"
|
||
+ "effect_duration INTEGER DEFAULT 999,"
|
||
+ "enter_time INTEGER NOT NULL)");
|
||
stmt.executeUpdate(
|
||
"CREATE INDEX IF NOT EXISTS idx_uuid "
|
||
+ "ON player_effects(uuid)");
|
||
stmt.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().severe(
|
||
"[防护] 数据库初始化失败: "
|
||
+ e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
// ==================== 效果名称中英文映射 ====================
|
||
|
||
/**
|
||
* 将中文效果名转为 Bukkit 能识别的英文名
|
||
*/
|
||
private String resolveEffectName(String name) {
|
||
if (name == null) return null;
|
||
String n = name.trim();
|
||
// 如果已经是英文或命名空间格式,直接用
|
||
if (n.contains(":")
|
||
|| n.equals(n.toUpperCase())) {
|
||
return n;
|
||
}
|
||
// 中文 → 英文映射
|
||
switch (n) {
|
||
case "伤害吸收": return "ABSORPTION";
|
||
case "生命提升": return "HEALTH_BOOST";
|
||
case "抗性提升": return "DAMAGE_RESISTANCE";
|
||
case "抗火": return "FIRE_RESISTANCE";
|
||
case "夜视": return "NIGHT_VISION";
|
||
case "力量": return "INCREASE_DAMAGE";
|
||
case "速度": return "SPEED";
|
||
case "急迫": return "FAST_DIGGING";
|
||
case "跳跃提升": return "JUMP";
|
||
case "再生": return "REGENERATION";
|
||
case "生命恢复": return "REGENERATION";
|
||
case "治疗": return "HEAL";
|
||
case "瞬间治疗": return "HEAL";
|
||
case "伤害药水": return "HARM";
|
||
case "瞬间伤害": return "HARM";
|
||
case "虚弱": return "WEAKNESS";
|
||
case "中毒": return "POISON";
|
||
case "缓慢": return "SLOW";
|
||
case "挖掘疲劳": return "SLOW_DIGGING";
|
||
case "失明": return "BLINDNESS";
|
||
case "隐身": return "INVISIBILITY";
|
||
case "发光": return "GLOWING";
|
||
// 村庄英雄
|
||
case "村庄英雄": return "HERO_OF_THE_VILLAGE";
|
||
case "幸运": return "LUCK";
|
||
case "不幸": return "UNLUCK";
|
||
case "水下呼吸": return "WATER_BREATHING";
|
||
case "水下速掘": return "DOLPHINS_GRACE";
|
||
case "缓降": return "LEVITATION";
|
||
case "漂浮": return "LEVITATION";
|
||
case "饱和": return "SATURATION";
|
||
// 无效效果
|
||
case "伤害": return "HARM";
|
||
case "治疗药水": return "HEAL";
|
||
default:
|
||
// 尝试直接用英文匹配
|
||
return n;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 全量容错:把全角字符统一转半角,
|
||
* 去掉零宽字符/不可见字符,
|
||
* 让用户怎么写都能正确解析。
|
||
*/
|
||
private String normalizeLine(String input) {
|
||
if (input == null || input.isEmpty()) return "";
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
for (int i = 0; i < input.length(); i++) {
|
||
char c = input.charAt(i);
|
||
|
||
// 跳过零宽字符和不可见字符
|
||
if (c == '\u200B' // 零宽空格
|
||
|| c == '\u200C' // 零宽非连接
|
||
|| c == '\u200D' // 零宽连接
|
||
|| c == '\uFEFF' // BOM
|
||
|| c == '\u00A0' // 不间断空格
|
||
|| c == '\u2028' // 行分隔符
|
||
|| c == '\u2029' // 段分隔符
|
||
|| c == '\u200E' // LTR标记
|
||
|| c == '\u200F' // RTL标记
|
||
|| c == '\u2060' // 字连接符
|
||
|| (c >= '\u2000' && c <= '\u200A')) { // 各种空格
|
||
continue;
|
||
}
|
||
|
||
// 全角→半角映射
|
||
if (c >= '\uFF01' && c <= '\uFF5E') {
|
||
c = (char) (c - 0xFEE0);
|
||
}
|
||
|
||
// 全角空格→半角空格
|
||
if (c == '\u3000') {
|
||
c = ' ';
|
||
}
|
||
|
||
sb.append(c);
|
||
}
|
||
|
||
return sb.toString();
|
||
}
|
||
|
||
|
||
private AreaConfig parseArea(String name, File f)
|
||
throws IOException {
|
||
AreaConfig ac = new AreaConfig();
|
||
ac.name = name;
|
||
BufferedReader br = new BufferedReader(
|
||
new InputStreamReader(
|
||
new FileInputStream(f),
|
||
StandardCharsets.UTF_8));
|
||
String line;
|
||
boolean inBlockComment = false;
|
||
boolean inHtmlComment = false;
|
||
|
||
while ((line = br.readLine()) != null) {
|
||
line = line.trim();
|
||
if (line.isEmpty()) continue;
|
||
// ===== 全量容错预处理:全角→半角,去零宽字符 =====
|
||
line = normalizeLine(line);
|
||
if (line.isEmpty()) continue;
|
||
// ===== 暴力调试:输出所有包含关键词的行 =====
|
||
if (line.indexOf("游戏") >= 0
|
||
|| line.indexOf("模式") >= 0
|
||
|| line.indexOf("生存") >= 0
|
||
|| line.indexOf("创造") >= 0
|
||
|| line.indexOf("排除") >= 0
|
||
|| line.indexOf("豁免") >= 0
|
||
|| line.indexOf("强制") >= 0
|
||
|| line.indexOf("GameMode") >= 0
|
||
|| line.indexOf("SURVIVAL") >= 0
|
||
|| line.indexOf("CREATIVE") >= 0) {
|
||
StringBuilder hex = new StringBuilder();
|
||
for (int i = 0; i < line.length(); i++) {
|
||
hex.append(String.format("U+%04X ", (int) line.charAt(i)));
|
||
}
|
||
/* plugin.getLogger().info("[暴力调试] 行=[" + line
|
||
+ "] 长度=" + line.length()
|
||
+ " 字符码=" + hex.toString());*/
|
||
}
|
||
// ===== 兜底扫描:确保和平白名单一定能解析 =====
|
||
if (line.indexOf("白名单") >= 0
|
||
|| line.indexOf("排除名单") >= 0
|
||
|| line.indexOf("模式排除") >= 0
|
||
|| line.indexOf("模式豁免") >= 0) {
|
||
int ci = line.indexOf(':');
|
||
if (ci < 0) ci = line.indexOf(':');
|
||
if (ci < 0) ci = line.indexOf('=');
|
||
if (ci < 0) ci = line.indexOf(' ');
|
||
|
||
// 和平白名单
|
||
if (line.indexOf("和平") >= 0
|
||
&& line.indexOf("白名单") >= 0
|
||
&& ci >= 0) {
|
||
String rest = line.substring(ci + 1).trim();
|
||
rest = rest.replaceAll("[,,;;|、/]", ",");
|
||
for (String s : rest.split(",")) {
|
||
String nm = s.trim();
|
||
if (!nm.isEmpty()) ac.peaceWhitelist.add(nm);
|
||
}
|
||
plugin.getLogger().info("[解析兜底] 和平白名单="
|
||
+ ac.peaceWhitelist);
|
||
}
|
||
|
||
// 排除名单
|
||
if (line.indexOf("排除名单") >= 0
|
||
|| line.indexOf("模式排除") >= 0
|
||
|| line.indexOf("模式豁免") >= 0) {
|
||
if (ci >= 0) {
|
||
String rest = line.substring(ci + 1).trim();
|
||
rest = rest.replaceAll("[,,;;|、/]", ",");
|
||
for (String s : rest.split(",")) {
|
||
String nm = s.trim();
|
||
if (!nm.isEmpty()) ac.modeExempt.add(nm);
|
||
}
|
||
plugin.getLogger().info("[解析兜底] 排除名单="
|
||
+ ac.modeExempt);
|
||
}
|
||
}
|
||
}
|
||
// ===== 兜底结束 =====
|
||
|
||
if (inBlockComment) {
|
||
int endIdx = line.indexOf("*/");
|
||
if (endIdx >= 0) {
|
||
inBlockComment = false;
|
||
line = line.substring(endIdx + 2).trim();
|
||
if (line.isEmpty()) continue;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
if (line.startsWith("/*")) {
|
||
int endIdx = line.indexOf("*/", 2);
|
||
if (endIdx >= 0) {
|
||
line = line.substring(endIdx + 2).trim();
|
||
if (line.isEmpty()) continue;
|
||
} else {
|
||
inBlockComment = true;
|
||
continue;
|
||
}
|
||
}
|
||
if (inHtmlComment) {
|
||
int endIdx = line.indexOf("-->");
|
||
if (endIdx >= 0) {
|
||
inHtmlComment = false;
|
||
line = line.substring(endIdx + 3).trim();
|
||
if (line.isEmpty()) continue;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
if (line.startsWith("<!--")) {
|
||
int endIdx = line.indexOf("-->", 4);
|
||
if (endIdx >= 0) {
|
||
line = line.substring(endIdx + 3).trim();
|
||
if (line.isEmpty()) continue;
|
||
} else {
|
||
inHtmlComment = true;
|
||
continue;
|
||
}
|
||
}
|
||
if (line.startsWith("#") || line.startsWith("//")) {
|
||
continue;
|
||
}
|
||
int hashIdx = line.indexOf("#");
|
||
if (hashIdx > 0) {
|
||
line = line.substring(0, hashIdx).trim();
|
||
}
|
||
int slashIdx = line.indexOf("//");
|
||
if (slashIdx > 0) {
|
||
line = line.substring(0, slashIdx).trim();
|
||
}
|
||
if (line.isEmpty()) continue;
|
||
|
||
if (line.startsWith("起点:")) {
|
||
String[] pp = line.substring("起点:".length())
|
||
.trim().split(",");
|
||
if (pp.length == 3) {
|
||
ac.world = pp[0].trim();
|
||
ac.x1 = Integer.parseInt(pp[1].trim());
|
||
ac.z1 = Integer.parseInt(pp[2].trim());
|
||
}
|
||
} else if (line.startsWith("终点:")) {
|
||
String[] pp = line.substring("终点:".length())
|
||
.trim().split(",");
|
||
if (pp.length == 3) {
|
||
ac.world = pp[0].trim();
|
||
ac.x2 = Integer.parseInt(pp[1].trim());
|
||
ac.z2 = Integer.parseInt(pp[2].trim());
|
||
}
|
||
} else if (line.startsWith("高度范围:")) {
|
||
String[] pp = line.substring("高度范围:".length())
|
||
.trim().split("-");
|
||
if (pp.length == 2) {
|
||
ac.yMin = Integer.parseInt(pp[0].trim());
|
||
ac.yMax = Integer.parseInt(pp[1].trim());
|
||
}
|
||
} else if (line.startsWith("没收物品:")) {
|
||
for (String s : line.substring("没收物品:".length())
|
||
.trim().split("[,,]"))
|
||
ac.confiscateItems.add(
|
||
s.trim().toUpperCase());
|
||
} else if (line.startsWith("禁止使用物品:")) {
|
||
for (String s : line.substring("禁止使用物品:".length())
|
||
.trim().split("[,,]"))
|
||
ac.denyUseItems.add(
|
||
s.trim().toUpperCase());
|
||
} else if (line.equals("禁止放置方块")) {
|
||
ac.denyBlockPlace = true;
|
||
} else if (line.equals("禁止破坏方块")) {
|
||
ac.denyBlockBreak = true;
|
||
} else if (line.equals("禁止PVP")) {
|
||
ac.denyPVP = true;
|
||
} else if (line.equals("禁止摔伤")) {
|
||
ac.denyFallDamage = true;
|
||
} else if (line.equals("禁止饥饿")) {
|
||
ac.denyHunger = true;
|
||
} else if (line.equals("禁止一切伤害")) {
|
||
ac.denyAllDamage = true;
|
||
} else if (line.equals("禁止丢弃物品")) {
|
||
ac.denyDrop = true;
|
||
} else if (line.equals("禁止末影珍珠")) {
|
||
ac.denyEnderPearl = true;
|
||
} else if (line.equals("禁止使用弓箭")) {
|
||
ac.denyBow = true;
|
||
} else if (line.equals("禁止骑乘")) {
|
||
ac.denyMount = true;
|
||
} else if (line.equals("禁止袭击")) {
|
||
ac.denyRaid = true;
|
||
} else if (line.equals("禁止药水")) {
|
||
ac.denyPotion = true;
|
||
} else if (line.equals("禁止爆炸")) {
|
||
ac.denyExplosion = true;
|
||
} else if (line.equals("禁止燃烧")
|
||
|| line.equals("禁止火焰蔓延")) {
|
||
ac.denyFireSpread = true;
|
||
} else if (line.equals("清除所有效果")
|
||
|| line.equals("清理所有效果")) {
|
||
ac.clearAllBadEffects = true;
|
||
} else if (line.startsWith("清除")
|
||
|| line.startsWith("清理")) {
|
||
String effName = line.substring(2).trim();
|
||
if (effName.endsWith("效果"))
|
||
effName = effName.substring(0,
|
||
effName.length() - 2);
|
||
if (!effName.isEmpty())
|
||
ac.clearEffects.add(effName);
|
||
} else if (line.contains("和平模式")
|
||
&& !line.contains("白名单")
|
||
&& !line.contains("定时")
|
||
&& !line.contains("开关")) {
|
||
ac.peaceMode = true;
|
||
plugin.getLogger().info("[解析] 和平模式已识别");
|
||
|
||
} else if (line.startsWith("效果")) {
|
||
String rest = line.substring(2).trim();
|
||
// 去掉前导冒号、空格
|
||
while (rest.length() > 0
|
||
&& (rest.charAt(0) == ':'
|
||
|| rest.charAt(0) == ':'
|
||
|| rest.charAt(0) == ' '
|
||
|| rest.charAt(0) == '\t')) {
|
||
rest = rest.substring(1);
|
||
}
|
||
if (!rest.isEmpty()) {
|
||
String[] parts = rest.split("\\s+");
|
||
// 第一个是效果名(可能是中文)
|
||
String effName = parts[0];
|
||
int effLv = 1;
|
||
int effDur = 999;
|
||
if (parts.length >= 2) {
|
||
try {
|
||
effLv = Integer.parseInt(parts[1]);
|
||
} catch (NumberFormatException ignored) {}
|
||
}
|
||
if (parts.length >= 3) {
|
||
try {
|
||
effDur = Integer.parseInt(parts[2]);
|
||
} catch (NumberFormatException ignored) {}
|
||
}
|
||
|
||
// ★ 用映射方法转为英文名
|
||
String resolved = resolveEffectName(effName);
|
||
plugin.getLogger().info("[防护] 解析效果: "
|
||
+ effName + " → " + resolved
|
||
+ " 等级=" + effLv
|
||
+ " 时长=" + effDur);
|
||
|
||
ac.giveEffects.add(new String[]{
|
||
resolved,
|
||
String.valueOf(effLv),
|
||
String.valueOf(effDur)});
|
||
// ★ 加日志确认解析结果
|
||
PotionEffectType checkType =
|
||
resolveEffectType(effName);
|
||
plugin.getLogger().info("[防护] 解析效果: "
|
||
+ effName + " → type="
|
||
+ (checkType != null
|
||
? checkType.getName()
|
||
: "NULL(失败)")
|
||
+ " 等级=" + effLv
|
||
+ " 时长=" + effDur);
|
||
|
||
|
||
}
|
||
if (!rest.isEmpty()) {
|
||
String[] parts = rest.split("\\s+");
|
||
int effLv = 1;
|
||
int effDur = 999;
|
||
if (parts.length >= 2) {
|
||
try {
|
||
effLv = Integer.parseInt(parts[1]);
|
||
} catch (NumberFormatException ignored) {
|
||
}
|
||
}
|
||
if (parts.length >= 3) {
|
||
try {
|
||
effDur = Integer.parseInt(parts[2]);
|
||
} catch (NumberFormatException ignored) {
|
||
}
|
||
}
|
||
ac.giveEffects.add(new String[]{
|
||
parts[0],
|
||
String.valueOf(effLv),
|
||
String.valueOf(effDur)});
|
||
}
|
||
} else if (line.startsWith("通报批评:")) {
|
||
ac.announceTemplate = line.substring(
|
||
"通报批评:".length()).trim();
|
||
ac.enableAnnounce = true;
|
||
} else if (line.startsWith("没收提示:")) {
|
||
ac.confiscateMsg = line.substring(
|
||
"没收提示:".length()).trim();
|
||
} else if (line.startsWith("进入提示:")) {
|
||
String raw = line.substring("进入提示:".length()).trim();
|
||
ac.enterMsg = raw;
|
||
|
||
// ★ 调试:打印原文和Unicode
|
||
StringBuilder hex = new StringBuilder();
|
||
for (int i = 0; i < raw.length(); i++) {
|
||
hex.append(String.format("U+%04X ",
|
||
(int) raw.charAt(i)));
|
||
}
|
||
plugin.getLogger().info(
|
||
"[防护-解析] ★★★进入提示原文=[" + raw
|
||
+ "] 长度=" + raw.length()
|
||
+ " Unicode=" + hex.toString());
|
||
} else if (line.startsWith("离开提示:")) {
|
||
String raw = line.substring("离开提示:".length()).trim();
|
||
ac.leaveMsg = raw;
|
||
|
||
StringBuilder hex = new StringBuilder();
|
||
for (int i = 0; i < raw.length(); i++) {
|
||
hex.append(String.format("U+%04X ",
|
||
(int) raw.charAt(i)));
|
||
}
|
||
plugin.getLogger().info(
|
||
"[防护-解析] ★★★离开提示原文=[" + raw
|
||
+ "] 长度=" + raw.length()
|
||
+ " Unicode=" + hex.toString());
|
||
} else if (line.startsWith("惩罚命令:")) {
|
||
ac.punishCommands.add(line.substring(
|
||
"惩罚命令:".length()).trim());
|
||
} else if (line.startsWith("强制游戏模式:")
|
||
|| line.startsWith("游戏模式:")) {
|
||
String prefix = line.startsWith("强制游戏模式:")
|
||
? "强制游戏模式:" : "游戏模式:";
|
||
String modeStr = line.substring(prefix.length())
|
||
.trim().toUpperCase();
|
||
// 容错:支持多种写法
|
||
if (modeStr.equals("CREATIVE") || modeStr.equals("创造")
|
||
|| modeStr.equals("1")) {
|
||
ac.enforceGameMode = "CREATIVE";
|
||
} else if (modeStr.equals("SURVIVAL") || modeStr.equals("生存")
|
||
|| modeStr.equals("0")) {
|
||
ac.enforceGameMode = "SURVIVAL";
|
||
} else if (modeStr.equals("ADVENTURE") || modeStr.equals("冒险")
|
||
|| modeStr.equals("2")) {
|
||
ac.enforceGameMode = "ADVENTURE";
|
||
} else if (modeStr.equals("SPECTATOR") || modeStr.equals("旁观")
|
||
|| modeStr.equals("spectator")
|
||
|| modeStr.equals("3")) {
|
||
ac.enforceGameMode = "SPECTATOR";
|
||
}
|
||
} else if (line.contains("排除名单")
|
||
|| line.contains("模式排除")
|
||
|| line.contains("模式豁免")) {
|
||
int colonIdx = line.indexOf(':');
|
||
if (colonIdx < 0) colonIdx = line.indexOf(':');
|
||
if (colonIdx < 0) colonIdx = line.indexOf('=');
|
||
if (colonIdx >= 0) {
|
||
String rest = line.substring(colonIdx + 1).trim();
|
||
String cleaned = rest.replaceAll("[,,;;|、/]", ",");
|
||
for (String s : cleaned.split(",")) {
|
||
String playerName = s.trim();
|
||
if (!playerName.isEmpty()) {
|
||
ac.modeExempt.add(playerName);
|
||
}
|
||
}
|
||
}
|
||
|
||
String rest = line.substring(/*idx*/ + 4).trim();
|
||
while (rest.length() > 0) {
|
||
char c = rest.charAt(0);
|
||
if (c == ':' || c == ':' || c == '='
|
||
|| c == ' ' || c == '\t') {
|
||
rest = rest.substring(1);
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if (!rest.isEmpty()) {
|
||
String cleaned = rest.replaceAll("[,,;;|、/]", ",");
|
||
for (String s : cleaned.split(",")) {
|
||
String playername = s.trim();
|
||
if (!name.isEmpty()) ac.modeExempt.add(name);
|
||
}
|
||
}
|
||
|
||
while (rest.length() > 0) {
|
||
char c = rest.charAt(0);
|
||
if (c == ':' || c == ':' || c == '='
|
||
|| c == ' ' || c == '\t') {
|
||
rest = rest.substring(1);
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
}
|
||
if (!rest.isEmpty()) {
|
||
String cleaned = rest.replaceAll("[,,;;|、/]", ",");
|
||
for (String s : cleaned.split(",")) {
|
||
String wlName = s.trim();
|
||
if (!wlName.isEmpty()) {
|
||
ac.peaceWhitelist.add(wlName);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
} else if (line.equals("禁止交互展示框")
|
||
|| line.equals("禁止展示框")) {
|
||
ac.denyItemFrame = true;
|
||
|
||
}
|
||
}
|
||
br.close();
|
||
return ac;
|
||
}
|
||
// ===== 和平白名单操作 =====
|
||
|
||
// 添加生物名字到区域和平白名单
|
||
public boolean addPeaceName(String areaName, String creatureName) {
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac == null) return false;
|
||
ac.peaceWhitelist.add(creatureName);
|
||
savePeaceWhitelist(ac);
|
||
return true;
|
||
}
|
||
|
||
// 从区域和平白名单移除生物名字
|
||
public boolean removePeaceName(String areaName, String creatureName) {
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac == null) return false;
|
||
ac.peaceWhitelist.remove(creatureName);
|
||
savePeaceWhitelist(ac);
|
||
return true;
|
||
}
|
||
|
||
// ===== 模式排除名单操作 =====
|
||
|
||
|
||
@EventHandler
|
||
public void onCreatureSpawn(CreatureSpawnEvent e) {
|
||
Entity entity = e.getEntity();
|
||
if (!isHostile(entity.getType().name())) return;
|
||
|
||
Location loc = entity.getLocation();
|
||
AreaConfig ac = getArea(
|
||
loc.getWorld().getName(),
|
||
loc.getBlockX(), loc.getBlockY(),
|
||
loc.getBlockZ());
|
||
|
||
if (ac == null || !ac.peaceMode) return;
|
||
|
||
protectedEntities.put(
|
||
entity.getUniqueId(),
|
||
System.currentTimeMillis() + 5000);
|
||
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ entity.getType().name()
|
||
+ " 获得5秒保护期"
|
||
+ " 区域=" + ac.name
|
||
+ " 白名单=" + ac.peaceWhitelist
|
||
+ " 白名单数量=" + ac.peaceWhitelist.size());*/
|
||
}
|
||
|
||
|
||
public void cleanupProtectedEntities() {
|
||
long now = System.currentTimeMillis();
|
||
int cleaned = 0;
|
||
Iterator<Map.Entry<UUID, Long>> it =
|
||
protectedEntities.entrySet().iterator();
|
||
while (it.hasNext()) {
|
||
Map.Entry<UUID, Long> entry = it.next();
|
||
if (now > entry.getValue()) {
|
||
it.remove();
|
||
cleaned++;
|
||
}
|
||
}
|
||
if (cleaned > 0) {
|
||
/* plugin.getLogger().info("[和平模式] 清理过期保护: "
|
||
+ cleaned + "个 剩余:"
|
||
+ protectedEntities.size() + "个");*/
|
||
}
|
||
}
|
||
|
||
|
||
public void banHostilesWithWhitelist(Player p, AreaConfig ac) {
|
||
long now = System.currentTimeMillis();
|
||
// 看看附近到底有什么实体
|
||
List<Entity> nearby = p.getNearbyEntities(48, 48, 48);
|
||
/* plugin.getLogger().info("[和平] " + p.getName()
|
||
+ " 附近实体总数=" + nearby.size());*/
|
||
for (Entity ent : nearby) {
|
||
String typeName = ent.getType().name();
|
||
/* plugin.getLogger().info("[和平] 实体: "
|
||
+ typeName + " isHostile=" + isHostile(typeName));*/
|
||
}
|
||
int scanned = 0;
|
||
int waiting = 0;
|
||
int saved = 0;
|
||
int banished = 0;
|
||
|
||
for (Entity ent : p.getNearbyEntities(48, 48, 48)) {
|
||
if (!isHostile(ent.getType().name())) continue;
|
||
scanned++;
|
||
|
||
UUID entUid = ent.getUniqueId();
|
||
Long protectUntil = protectedEntities.get(entUid);
|
||
String customName = ent.getCustomName();
|
||
|
||
// 有保护期记录
|
||
if (protectUntil != null) {
|
||
long remain = protectUntil - now;
|
||
|
||
if (remain > 0) {
|
||
// 保护期内,精确剩余秒数
|
||
int secondsLeft = (int) Math.ceil(remain / 1000.0);
|
||
waiting++;
|
||
|
||
// 保护期内也检查名字(玩家可能刚贴上)
|
||
if (customName != null
|
||
&& !customName.isEmpty()
|
||
&& ac.peaceWhitelist.contains(customName)) {
|
||
protectedEntities.remove(entUid);
|
||
saved++;
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 命名[" + customName
|
||
+ "] 白名单匹配,永久保留");*/
|
||
continue;
|
||
}
|
||
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 保护期剩余约" + secondsLeft + "秒");*/
|
||
continue;
|
||
}
|
||
|
||
// 保护期到了,最终检查
|
||
protectedEntities.remove(entUid);
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name() + " 5秒到期");*/
|
||
|
||
// 到期时再查一次白名单
|
||
if (customName != null
|
||
&& !customName.isEmpty()
|
||
&& ac.peaceWhitelist.contains(customName)) {
|
||
saved++;
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 命名[" + customName
|
||
+ "] 到期但白名单匹配,保留");*/
|
||
continue;
|
||
}
|
||
|
||
// 没名字或不在白名单,传送虚空
|
||
Location voidLoc = ent.getLocation().clone();
|
||
voidLoc.setY(ent.getWorld().getMinHeight() - 50);
|
||
ent.teleport(voidLoc);
|
||
banished++;
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 命名=[" + customName + "] 传送虚空");*/
|
||
continue;
|
||
}
|
||
|
||
// 没有保护期记录(非刷怪蛋生成的或记录被清了)
|
||
// 也检查一下白名单
|
||
if (customName != null
|
||
&& !customName.isEmpty()
|
||
&& ac.peaceWhitelist.contains(customName)) {
|
||
saved++;
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 命名[" + customName
|
||
+ "] 无保护期但白名单匹配,保留");*/
|
||
continue;
|
||
}
|
||
|
||
// 没有保护期且不在白名单,传送虚空
|
||
Location voidLoc = ent.getLocation().clone();
|
||
voidLoc.setY(ent.getWorld().getMinHeight() - 50);
|
||
ent.teleport(voidLoc);
|
||
banished++;
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ ent.getType().name()
|
||
+ " 无保护期,命名=[" + customName + "] 传送虚空");*/
|
||
}
|
||
|
||
/* plugin.getLogger().info("[和平] "
|
||
+ " 扫描=" + scanned
|
||
+ " 等待=" + waiting
|
||
+ " 保留=" + saved
|
||
+ " 传送=" + banished
|
||
+ " 白名单=" + ac.peaceWhitelist);*/
|
||
}
|
||
|
||
private static final Set<String> HOSTILE_TYPES = new HashSet<>(Arrays.asList(
|
||
"ZOMBIE", "ZOMBIE_VILLAGER", "HUSK", "DROWNED",
|
||
"SKELETON", "STRAY", "BOGGED", "WITHER_SKELETON",
|
||
"CREEPER", "SPIDER", "CAVE_SPIDER", "ENDERMITE",
|
||
"SILVERFISH", "BLAZE", "GHAST", "BIG_GHAST",
|
||
"MAGMA_CUBE", "SLIME", "WITCH",
|
||
"GUARDIAN", "ELDER_GUARDIAN",
|
||
"PILLAGER", "VINDICATOR", "EVOKER", "RAVAGER",
|
||
"ILLUSIONER", "VEX",
|
||
"PHANTOM", "SHULKER", "WARDEN", "BREEZE",
|
||
"PIGLIN", "PIGLIN_BRUTE", "ZOGLIN",
|
||
"ZOMBIFIED_PIGLIN", "ENDER_DRAGON"
|
||
));
|
||
|
||
private boolean isHostile(String typeName) {
|
||
return HOSTILE_TYPES.contains(typeName);
|
||
}
|
||
|
||
// ==================== 白名单持久化 ====================
|
||
|
||
/**
|
||
* 保存白名单到文件
|
||
* 路径:区域防护/whitelists/全局白名单.txt
|
||
* 路径:区域防护/whitelists/区域名.txt
|
||
*/
|
||
public void saveWhitelists() {
|
||
try {
|
||
// 1. 保存全局白名单
|
||
PrintWriter pwGlobal = new PrintWriter(
|
||
new OutputStreamWriter(
|
||
new FileOutputStream(globalWhitelistFile),
|
||
StandardCharsets.UTF_8));
|
||
for (String name : globalPlayerWhitelist) {
|
||
pwGlobal.println(name);
|
||
}
|
||
pwGlobal.close();
|
||
|
||
// 2. 删除旧的区域白名单文件(防止残留)
|
||
File[] oldFiles = whitelistDir.listFiles(
|
||
(d, n) -> n.endsWith(".txt")
|
||
&& !n.equals("全局白名单.txt"));
|
||
if (oldFiles != null) {
|
||
for (File f : oldFiles) f.delete();
|
||
}
|
||
|
||
// 3. 保存各区域白名单
|
||
for (Map.Entry<String, Set<String>> entry
|
||
: areaPlayerWhitelist.entrySet()) {
|
||
String name = entry.getKey();
|
||
File areaFile = new File(
|
||
whitelistDir, name + ".txt");
|
||
PrintWriter pwArea = new PrintWriter(
|
||
new OutputStreamWriter(
|
||
new FileOutputStream(areaFile),
|
||
StandardCharsets.UTF_8));
|
||
for (String playerName : entry.getValue()) {
|
||
pwArea.println(playerName);
|
||
}
|
||
pwArea.close();
|
||
}
|
||
|
||
plugin.getLogger().info("[防护] 白名单已保存");
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 保存白名单失败: "
|
||
+ e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// ==================== 区域检测 ====================
|
||
|
||
public AreaConfig getArea(String worldName,
|
||
int x, int y, int z) {
|
||
for (AreaConfig ac : areas.values()) {
|
||
if (ac.world.equalsIgnoreCase(worldName)
|
||
&& inRect(x, z, ac)
|
||
&& y >= ac.yMin
|
||
&& y <= ac.yMax) {
|
||
return ac;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private boolean inRect(int x, int z, AreaConfig ac) {
|
||
int minX = Math.min(ac.x1, ac.x2);
|
||
int maxX = Math.max(ac.x1, ac.x2);
|
||
int minZ = Math.min(ac.z1, ac.z2);
|
||
int maxZ = Math.max(ac.z1, ac.z2);
|
||
return x >= minX && x <= maxX
|
||
&& z >= minZ && z <= maxZ;
|
||
}
|
||
|
||
// ==================== 白名单检查 ====================
|
||
|
||
private boolean isPlayerWhitelisted(String player,
|
||
AreaConfig ac) {
|
||
if (globalPlayerWhitelist.contains(
|
||
player.toLowerCase()))
|
||
return true;
|
||
Set<String> areaList =
|
||
areaPlayerWhitelist.get(ac.name);
|
||
return areaList != null
|
||
&& areaList.contains(player.toLowerCase());
|
||
}
|
||
|
||
private boolean isItemBlacklisted(String itemId,
|
||
AreaConfig ac) {
|
||
if (globalItemBlacklist.contains(
|
||
itemId.toUpperCase()))
|
||
return true;
|
||
Set<String> areaList =
|
||
areaItemBlacklist.get(ac.name);
|
||
return areaList != null
|
||
&& areaList.contains(itemId.toUpperCase());
|
||
}
|
||
|
||
// ==================== 没收 ====================
|
||
|
||
private int confiscateItem(Player p, String itemTypeId) {
|
||
Material mat = Material.matchMaterial(itemTypeId);
|
||
if (mat == null) return 0;
|
||
ItemStack[] contents = p.getInventory().getContents();
|
||
int removed = 0;
|
||
for (int i = 0; i < contents.length; i++) {
|
||
if (contents[i] != null
|
||
&& contents[i].getType() == mat) {
|
||
removed += contents[i].getAmount();
|
||
contents[i] = null;
|
||
}
|
||
}
|
||
p.getInventory().setContents(contents);
|
||
return removed;
|
||
}
|
||
|
||
// ==================== 边框显示 ====================
|
||
private void showBorder(Player p, AreaConfig ac, boolean temp) {
|
||
cancelBorder(p);
|
||
int taskId = Bukkit.getScheduler()
|
||
.runTaskTimer(plugin, new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
if (!p.isOnline()) return;
|
||
Location loc = p.getLocation();
|
||
int px = loc.getBlockX();
|
||
int py = loc.getBlockY();
|
||
int pz = loc.getBlockZ();
|
||
int minX = Math.min(ac.x1, ac.x2);
|
||
int maxX = Math.max(ac.x1, ac.x2);
|
||
int minZ = Math.min(ac.z1, ac.z2);
|
||
int maxZ = Math.max(ac.z1, ac.z2);
|
||
for (int dy = 5; dy <= 30; dy += 5) {
|
||
for (int x = minX; x <= maxX; x += 4) {
|
||
for (int z = minZ; z <= maxZ; z += 4) {
|
||
if (x == minX || x == maxX
|
||
|| z == minZ || z == maxZ) {
|
||
p.spawnParticle(Particle.END_ROD,
|
||
x + 0.5, py + dy, z + 0.5,
|
||
1, 0, 0, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}, 0L, 20L).getTaskId();
|
||
displayTaskIds.put(p.getUniqueId(), taskId); // taskId 是 int
|
||
if (temp) {
|
||
Bukkit.getScheduler().runTaskLater(plugin,
|
||
() -> cancelBorder(p), 300L);
|
||
}
|
||
}
|
||
|
||
|
||
private void cancelBorder(Player p) {
|
||
Integer taskId = displayTaskIds.remove(p.getUniqueId());
|
||
if (taskId != null) {
|
||
Bukkit.getScheduler().cancelTask(taskId);
|
||
}
|
||
}
|
||
|
||
public void onPlayerQuit(String playerName) {
|
||
playerCurrentArea.remove(playerName);
|
||
}
|
||
|
||
public void clearAllPlayerAreas() {
|
||
playerCurrentArea.clear();
|
||
}
|
||
private void saveModeExempt(AreaConfig ac) {
|
||
File file = new File(rootDir, ac.name + ".txt");
|
||
if (!file.exists()) return;
|
||
|
||
try {
|
||
List<String> lines = new ArrayList<>();
|
||
BufferedReader reader = new BufferedReader(
|
||
new FileReader(file, StandardCharsets.UTF_8));
|
||
String line;
|
||
boolean found = false;
|
||
|
||
while ((line = reader.readLine()) != null) {
|
||
String trimmed = line.trim();
|
||
if (trimmed.contains("排除名单")
|
||
|| trimmed.contains("模式排除")
|
||
|| trimmed.contains("模式豁免")) {
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append("排除名单:");
|
||
boolean first = true;
|
||
for (String name : ac.modeExempt) {
|
||
if (!first) sb.append(",");
|
||
sb.append(name);
|
||
first = false;
|
||
}
|
||
lines.add(sb.toString());
|
||
found = true;
|
||
} else {
|
||
lines.add(line);
|
||
}
|
||
}
|
||
reader.close();
|
||
|
||
if (!found && !ac.modeExempt.isEmpty()) {
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append("排除名单:");
|
||
boolean first = true;
|
||
for (String name : ac.modeExempt) {
|
||
if (!first) sb.append(",");
|
||
sb.append(name);
|
||
first = false;
|
||
}
|
||
lines.add(sb.toString());
|
||
}
|
||
|
||
FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8);
|
||
for (String l : lines) {
|
||
writer.write(l + "\n");
|
||
}
|
||
writer.close();
|
||
} catch (IOException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 保存模式排除名单失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// ==================== 事件监听 ====================
|
||
@EventHandler
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
Player p = event.getPlayer();
|
||
UUID uid = p.getUniqueId();
|
||
Location to = event.getTo();
|
||
if (to == null) return;
|
||
|
||
Location from = event.getFrom();
|
||
if (from.getBlockX() == to.getBlockX()
|
||
&& from.getBlockZ() == to.getBlockZ()) {
|
||
return;
|
||
}
|
||
|
||
// 防抖
|
||
Long lastTime = lastMoveProcess.get(uid);
|
||
long now = System.currentTimeMillis();
|
||
if (lastTime != null
|
||
&& now - lastTime < MOVE_DEBOUNCE_MS) {
|
||
return;
|
||
}
|
||
lastMoveProcess.put(uid, now);
|
||
|
||
// 检测区域
|
||
String oldArea = playerCurrentArea.get(uid);
|
||
String newArea = null;
|
||
String forcedMode = null;
|
||
|
||
for (Map.Entry<String, AreaConfig> entry
|
||
: areas.entrySet()) {
|
||
AreaConfig ac = entry.getValue();
|
||
if (!ac.world.equals(to.getWorld().getName()))
|
||
continue;
|
||
int px = to.getBlockX();
|
||
int pz = to.getBlockZ();
|
||
int py = to.getBlockY();
|
||
int minX = Math.min(ac.x1, ac.x2);
|
||
int maxX = Math.max(ac.x1, ac.x2);
|
||
int minZ = Math.min(ac.z1, ac.z2);
|
||
int maxZ = Math.max(ac.z1, ac.z2);
|
||
if (px >= minX && px <= maxX
|
||
&& pz >= minZ && pz <= maxZ
|
||
&& py >= ac.yMin && py <= ac.yMax) {
|
||
newArea = entry.getKey();
|
||
forcedMode = ac.enforceGameMode;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动] " + p.getName()
|
||
+ " 旧=[" + oldArea
|
||
+ "] 新=[" + newArea + "]"
|
||
+ " areas.size=" + areas.size()
|
||
+ " world=" + to.getWorld().getName());*/
|
||
|
||
// ===== 2. 区域没变 =====
|
||
if (Objects.equals(oldArea, newArea)) {
|
||
if (newArea != null) {
|
||
AreaConfig ac = areas.get(newArea);
|
||
if (ac != null) {
|
||
// ★ 每次移动都清除指定效果
|
||
clearBadEffects(p, ac);
|
||
|
||
// ★ 每次移动都强制游戏模式
|
||
if (ac.enforceGameMode != null
|
||
&& !ac.enforceGameMode.isEmpty()
|
||
&& !isExemptFromGameMode(p, newArea)) {
|
||
GameMode target =
|
||
GameMode.valueOf(ac.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 离开旧区域
|
||
if (oldArea != null) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动-离开] ★★★开始离开处理:
|
||
+ oldArea);*/
|
||
|
||
AreaConfig oldAc = areas.get(oldArea);
|
||
|
||
// 离开提示
|
||
if (oldAc != null && oldAc.leaveMsg != null
|
||
&& !oldAc.leaveMsg.isEmpty()) {
|
||
p.sendMessage(formatAreaMsg(oldAc.leaveMsg));
|
||
}
|
||
|
||
// 收标
|
||
List<PotionEffectType> marked =
|
||
playerMarkedEffects.remove(uid);
|
||
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动-离开] ★★★收标: "
|
||
+ (marked != null ? marked.size() : "null"));*/
|
||
|
||
if (marked != null) {
|
||
for (PotionEffectType t : marked) {
|
||
plugin.getLogger().info(
|
||
"[防护-移动-离开] 标记项: " + t.getName());
|
||
}
|
||
}
|
||
|
||
// 取消旧延时
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
pendingClearArea.remove(uid);
|
||
pendingClearEffects.remove(uid);
|
||
plugin.getLogger().info(
|
||
"[防护-移动-离开] 取消旧延时");
|
||
}
|
||
|
||
// 创建延时清理
|
||
if (marked != null && !marked.isEmpty()) {
|
||
String clearArea = oldArea;
|
||
UUID clearUid = uid;
|
||
List<PotionEffectType> toClear =
|
||
new ArrayList<>(marked);
|
||
pendingClearArea.put(uid, clearArea);
|
||
pendingClearEffects.put(uid, toClear);
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动-离开] ★★★创建延时任务: "
|
||
+ toClear.size() + "个");*/
|
||
|
||
BukkitTask task = new BukkitRunnable() {
|
||
@Override
|
||
public void run() {
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动-清理] ★★★延时触发!");*/
|
||
pendingClearTask.remove(clearUid);
|
||
pendingClearArea.remove(clearUid);
|
||
|
||
Player online =
|
||
Bukkit.getPlayer(clearUid);
|
||
|
||
plugin.getLogger().info(
|
||
"[防护-移动-清理] 在线="
|
||
+ (online != null && online.isOnline())
|
||
+ " 效果数=" + toClear.size());
|
||
|
||
if (online == null || !online.isOnline()) {
|
||
removePlayerEffects(
|
||
clearUid, clearArea);
|
||
return;
|
||
}
|
||
|
||
int cleared = 0;
|
||
for (PotionEffectType type : toClear) {
|
||
PotionEffect eff =
|
||
online.getPotionEffect(type);
|
||
/* plugin.getLogger().info(
|
||
"[防护-移动-清理] 检查: "
|
||
+ type.getName()
|
||
+ " 身上=" + (eff != null));*/
|
||
if (eff != null) {
|
||
online.removePotionEffect(type);
|
||
cleared++;
|
||
}
|
||
}
|
||
removePlayerEffects(clearUid, clearArea);
|
||
plugin.getLogger().info(
|
||
"[防护-移动-清理] + cleared" + toClear.size());
|
||
}
|
||
}.runTaskLater(plugin, 300L);
|
||
pendingClearTask.put(uid, task);
|
||
}
|
||
}
|
||
|
||
|
||
// 进入新区域
|
||
if (newArea != null) {
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
String oldP = pendingClearArea.remove(uid);
|
||
if (oldP != null) {
|
||
diffEffects(p, uid, oldP, newArea);
|
||
}
|
||
}
|
||
|
||
AreaConfig newAc = areas.get(newArea);
|
||
if (newAc != null) {
|
||
if (newAc.enterMsg != null
|
||
&& !newAc.enterMsg.isEmpty()) {
|
||
p.sendMessage(
|
||
formatAreaMsg(newAc.enterMsg));
|
||
}
|
||
applyRegionEffects(p, uid, newArea, newAc);
|
||
// 清除效果
|
||
clearBadEffects(p, newAc);
|
||
|
||
}
|
||
// ===== 清除指定效果 =====
|
||
if (newAc.clearEffects != null
|
||
&& !newAc.clearEffects.isEmpty()) {
|
||
for (String effName : newAc.clearEffects) {
|
||
PotionEffectType type =
|
||
resolveEffectType(effName);
|
||
if (type != null && p.getPotionEffect(type) != null) {
|
||
p.removePotionEffect(type);
|
||
plugin.getLogger().info(
|
||
"[防护] ★清除效果: " + effName
|
||
+ " 玩家=" + p.getName());
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===== 清除所有效果 =====
|
||
if (newAc.clearAllBadEffects) {
|
||
for (PotionEffect eff : p.getActivePotionEffects()) {
|
||
p.removePotionEffect(eff.getType());
|
||
}
|
||
plugin.getLogger().info(
|
||
"[防护] ★清除所有效果, 玩家=" + p.getName());
|
||
}
|
||
|
||
if (forcedMode != null
|
||
&& !isExemptFromGameMode(p, newArea)) {
|
||
GameMode target =
|
||
GameMode.valueOf(forcedMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (newArea != null) {
|
||
playerCurrentArea.put(uid, newArea);
|
||
} else {
|
||
playerCurrentArea.remove(uid);
|
||
}
|
||
}
|
||
// ===== 清理袭击生物 =====
|
||
|
||
// 需要清理的袭击生物类型
|
||
private static final Set<String> RAID_MOBS = new HashSet<>(Arrays.asList(
|
||
"VEX",
|
||
"EVOKER",
|
||
"PILLAGER",
|
||
"VINDICATOR",
|
||
"WITCH",
|
||
"RAVAGER",
|
||
"PILLAGER"
|
||
));
|
||
|
||
@EventHandler
|
||
public void onEntitySpawn(EntitySpawnEvent event) {
|
||
Entity entity = event.getEntity();
|
||
Location loc = entity.getLocation();
|
||
|
||
// 检查是否在防护区域内
|
||
AreaConfig ac = getArea(
|
||
loc.getWorld().getName(),
|
||
loc.getBlockX(), loc.getBlockY(),
|
||
loc.getBlockZ());
|
||
if (ac == null) return;
|
||
|
||
// 禁止袭击:取消袭击事件
|
||
if (ac.denyRaid) {
|
||
// 清理袭击生物
|
||
String typeName = entity.getType().name();
|
||
if (RAID_MOBS.contains(typeName)) {
|
||
event.setCancelled(true);
|
||
plugin.getLogger().info(
|
||
"[防护] ★清理袭击生物: " + typeName
|
||
+ " 区域=" + ac.name);
|
||
}
|
||
}
|
||
|
||
// 没收物品检测
|
||
if (ac.confiscateItems != null
|
||
&& !ac.confiscateItems.isEmpty()) {
|
||
if (entity instanceof Item) {
|
||
Item item = (Item) entity;
|
||
String itemType = item.getItemStack()
|
||
.getType().name();
|
||
if (ac.confiscateItems.contains(itemType)) {
|
||
event.setCancelled(true);
|
||
plugin.getLogger().info(
|
||
"[防护] 没收违禁掉落物: "
|
||
+ itemType);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 进入区域时发放效果
|
||
* 流程:查DB → 有记录跳过写入 → 无记录写入DB
|
||
* → 给效果 → 贴标
|
||
*/
|
||
private void applyRegionEffects(Player p, UUID uid,
|
||
String areaName, AreaConfig ac) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-贴标] ★★★方法被调用! 玩家="
|
||
+ p.getName() + " 区域=" + areaName
|
||
+ " ac=" + (ac != null)
|
||
+ " 效果配置数="
|
||
+ (ac != null ? ac.giveEffects.size() : "null"));*/
|
||
|
||
if (ac == null || ac.giveEffects.isEmpty()) return;
|
||
if (ac.giveEffects.isEmpty()) return;
|
||
// 查DB:该玩家在此区域是否已有效果记录
|
||
List<String> dbEffects =
|
||
getPlayerEffectNames(uid, areaName);
|
||
|
||
if (dbEffects.isEmpty()) {
|
||
// 无记录 → 写入DB
|
||
for (String[] eff : ac.giveEffects) {
|
||
PotionEffectType t =
|
||
resolveEffectType(eff[0]);
|
||
if (t == null) continue;
|
||
saveSingleEffect(uid, p.getName(),
|
||
areaName, t.getName());
|
||
}
|
||
plugin.getLogger().info(
|
||
"[防护] DB写入, 玩家=" + p.getName()
|
||
+ " 区域=" + areaName);
|
||
} else {
|
||
plugin.getLogger().info(
|
||
"[防护] DB已有记录, 跳过写入, 玩家="
|
||
+ p.getName()
|
||
+ " 区域=" + areaName);
|
||
}
|
||
|
||
// 给效果 + 贴标
|
||
List<PotionEffectType> applied = new ArrayList<>();
|
||
for (String[] eff : ac.giveEffects) {
|
||
try {
|
||
PotionEffectType type =
|
||
resolveEffectType(eff[0]);
|
||
if (type == null) continue;
|
||
int lv = Integer.parseInt(eff[1]) - 1;
|
||
int dur = Integer.parseInt(eff[2]) * 20;
|
||
|
||
PotionEffect existing =
|
||
p.getPotionEffect(type);
|
||
if (existing != null
|
||
&& existing.getAmplifier() >= lv) {
|
||
continue;
|
||
}
|
||
p.addPotionEffect(
|
||
new PotionEffect(type, dur, lv));
|
||
applied.add(type);
|
||
} catch (Exception ignored) {
|
||
}
|
||
}
|
||
|
||
// 贴标
|
||
// 新代码(合并,不覆盖):
|
||
List<PotionEffectType> existingMarks =
|
||
playerMarkedEffects.get(uid);
|
||
if (existingMarks != null && !existingMarks.isEmpty()) {
|
||
// 已有标记,合并新增的
|
||
for (PotionEffectType t : applied) {
|
||
if (!existingMarks.contains(t)) {
|
||
existingMarks.add(t);
|
||
}
|
||
}
|
||
/* plugin.getLogger().info(
|
||
"[防护-贴标] 合并标记: "
|
||
+ existingMarks.size()
|
||
+ " 个, 玩家=" + p.getName());*/
|
||
} else {
|
||
// 无标记,新建
|
||
playerMarkedEffects.put(uid, applied);
|
||
/* plugin.getLogger().info(
|
||
"[防护-贴标] 新建标记: "
|
||
+ applied.size()
|
||
+ " 个, 玩家=" + p.getName());*/
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 离开区域时,延时15秒清理效果
|
||
* 流程:收标 → 15秒后清效果 → 确认清空 → 清DB
|
||
*/
|
||
private void scheduleEffectClear(Player p, UUID uid,
|
||
String areaName) {
|
||
// 收标
|
||
List<PotionEffectType> marked =
|
||
playerAppliedEffects.remove(uid);
|
||
|
||
plugin.getLogger().info(
|
||
"[防护] 收标 " + (marked != null ? marked.size() : 0)
|
||
+ " 个, 玩家=" + p.getName()
|
||
+ " 区域=" + areaName);
|
||
|
||
// 启动15秒延时
|
||
pendingClearArea.put(uid, areaName);
|
||
BukkitTask task = new BukkitRunnable() {
|
||
@Override
|
||
public void run() {
|
||
pendingClearTask.remove(uid);
|
||
pendingClearArea.remove(uid);
|
||
pendingClearEffects.remove(uid);
|
||
|
||
// 清除效果
|
||
if (marked != null) {
|
||
for (PotionEffectType type : marked) {
|
||
p.removePotionEffect(type);
|
||
}
|
||
}
|
||
|
||
// 确认清空后清DB
|
||
removePlayerEffects(uid, areaName);
|
||
plugin.getLogger().info(
|
||
"[防护] 15秒已到, 清除 "
|
||
+ (marked != null ? marked.size() : 0)
|
||
+ " 个效果 + DB, 玩家="
|
||
+ p.getName()
|
||
+ " 区域=" + areaName);
|
||
}
|
||
}.runTaskLater(plugin, 300L); // 15秒 = 300 tick
|
||
|
||
pendingClearTask.put(uid, task);
|
||
}
|
||
public Set<String> getAllAreaNames() {
|
||
return areas.keySet();
|
||
}
|
||
|
||
/**
|
||
* 15秒内进了新区域:
|
||
* 清除旧区域有、新区域没有的效果
|
||
*/
|
||
private void diffEffects(Player p, UUID uid,
|
||
String oldAreaName, String newAreaName) {
|
||
AreaConfig oldAc = areas.get(oldAreaName);
|
||
AreaConfig newAc = areas.get(newAreaName);
|
||
if (oldAc == null || newAc == null) return;
|
||
|
||
// ★ 从 pendingClearEffects 获取旧标记
|
||
List<PotionEffectType> oldMarked =
|
||
pendingClearEffects.remove(uid);
|
||
|
||
if (oldMarked == null || oldMarked.isEmpty()) {
|
||
plugin.getLogger().info(
|
||
"[防护-跨区域] 无待清理效果");
|
||
return;
|
||
}
|
||
|
||
// 新区域的效果集合
|
||
Set<PotionEffectType> newTypes = new HashSet<>();
|
||
if (newAc != null) {
|
||
for (String[] eff : newAc.giveEffects) {
|
||
PotionEffectType t =
|
||
resolveEffectType(eff[0]);
|
||
if (t != null) newTypes.add(t);
|
||
}
|
||
}
|
||
|
||
int cleared = 0;
|
||
for (PotionEffectType type : oldMarked) {
|
||
if (!newTypes.contains(type)) {
|
||
p.removePotionEffect(type);
|
||
cleared++;
|
||
plugin.getLogger().info(
|
||
"[防护-跨区域] 清除: "
|
||
+ type.getName());
|
||
} else {
|
||
plugin.getLogger().info(
|
||
"[防护-跨区域] 保留: "
|
||
+ type.getName()
|
||
+ " (新区域也有)");
|
||
}
|
||
}
|
||
|
||
removePlayerEffects(uid, oldAreaName);
|
||
plugin.getLogger().info(
|
||
"[防护-跨区域] 完成: 清除"
|
||
+ cleared + "/" + oldMarked.size()
|
||
+ " 个, " + oldAreaName
|
||
+ " → " + newAreaName);
|
||
}
|
||
|
||
// ===== DB 辅助方法(简化版)=====
|
||
|
||
/**
|
||
* 查询玩家在某个区域的效果记录(返回英文名列表)
|
||
*/
|
||
private List<String> getPlayerEffectNames(
|
||
UUID uid, String areaName) {
|
||
List<String> list = new ArrayList<>();
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"SELECT effect_type FROM player_effects "
|
||
+ "WHERE uuid=? AND area_name=?");
|
||
ps.setString(1, uid.toString());
|
||
ps.setString(2, areaName);
|
||
ResultSet rs = ps.executeQuery();
|
||
while (rs.next()) {
|
||
list.add(rs.getString("effect_type"));
|
||
}
|
||
rs.close();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 查询效果失败: " + e.getMessage());
|
||
}
|
||
return list;
|
||
}
|
||
|
||
/**
|
||
* 写入单条效果记录
|
||
*/
|
||
private void saveSingleEffect(UUID uid,
|
||
String playerName, String areaName,
|
||
String effectType) {
|
||
try {
|
||
PreparedStatement ps = dbConnection.prepareStatement(
|
||
"INSERT INTO player_effects "
|
||
+ "(uuid, player_name, area_name, "
|
||
+ "effect_type, effect_level, "
|
||
+ "effect_duration, enter_time) "
|
||
+ "VALUES (?,?,?,?,?,?,?)");
|
||
ps.setString(1, uid.toString());
|
||
ps.setString(2, playerName);
|
||
ps.setString(3, areaName);
|
||
ps.setString(4, effectType);
|
||
ps.setInt(5, 0);
|
||
ps.setInt(6, 999);
|
||
ps.setLong(7, System.currentTimeMillis());
|
||
ps.executeUpdate();
|
||
ps.close();
|
||
} catch (SQLException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 写入效果失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// ==================== 违禁品没收 ====================
|
||
|
||
/**
|
||
* 检查玩家是否豁免于没收
|
||
* 全局白名单 / 区域白名单 / modeExempt / OP 都跳过
|
||
*/
|
||
private boolean isExemptFromConfiscation(
|
||
Player p, String areaName) {
|
||
String name = p.getName();
|
||
|
||
if (globalPlayerWhitelist.contains(name))
|
||
return true;
|
||
|
||
Set<String> areaWl =
|
||
areaPlayerWhitelist.get(areaName);
|
||
if (areaWl != null && areaWl.contains(name))
|
||
return true;
|
||
|
||
AreaConfig ac = areas.get(areaName);
|
||
if (ac != null && ac.modeExempt.contains(name))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 没收玩家背包中的违禁品
|
||
*/
|
||
private void confiscateItems(Player p,
|
||
AreaConfig ac) {
|
||
boolean confiscated = false;
|
||
|
||
for (int i = 0;
|
||
i < p.getInventory().getSize(); i++) {
|
||
ItemStack item = p.getInventory().getItem(i);
|
||
if (item == null) continue;
|
||
|
||
String typeName = item.getType().name();
|
||
if (ac.confiscateItems
|
||
.contains(typeName)) {
|
||
p.getInventory().setItem(i, null);
|
||
confiscated = true;
|
||
}
|
||
}
|
||
|
||
if (confiscated) {
|
||
String msg = ac.confiscateMsg != null
|
||
&& !ac.confiscateMsg.isEmpty()
|
||
? ac.confiscateMsg
|
||
: "§c你的违禁品已被没收";
|
||
p.sendMessage(msg);
|
||
|
||
// 通报批评
|
||
if (ac.enableAnnounce
|
||
&& ac.announceTemplate != null) {
|
||
String text = ac.announceTemplate
|
||
.replace("{player}",
|
||
p.getName());
|
||
Bukkit.broadcastMessage(text);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private String getChineseGameMode(GameMode mode) {
|
||
switch (mode) {
|
||
case SURVIVAL: return "生存模式";
|
||
case CREATIVE: return "创造模式";
|
||
case ADVENTURE: return "冒险模式";
|
||
case SPECTATOR: return "旁观模式";
|
||
default: return mode.name();
|
||
}
|
||
}
|
||
|
||
private void handleEnter(Player p, AreaConfig ac) {
|
||
UUID uid = p.getUniqueId();
|
||
|
||
// 清除负面效果
|
||
clearBadEffects(p, ac);
|
||
|
||
// 给予效果
|
||
List<PotionEffectType> given = new ArrayList<>();
|
||
for (String[] parts : ac.giveEffects) {
|
||
String effName = parts[0];
|
||
int level = 1;
|
||
int duration = 999;
|
||
try { level = Integer.parseInt(parts[1]); }
|
||
catch (NumberFormatException ignored) {}
|
||
try { duration = Integer.parseInt(parts[2]); }
|
||
catch (NumberFormatException ignored) {}
|
||
PotionEffectType type = resolveEffectType(effName);
|
||
if (type != null) {
|
||
p.addPotionEffect(new PotionEffect(
|
||
type, duration * 20, level - 1));
|
||
given.add(type);
|
||
}
|
||
}
|
||
|
||
removePlayerEffects(uid, ac.name);
|
||
if (!given.isEmpty()) {
|
||
// savePlayerEffects(uid, p.getName(), ac.name, given);
|
||
}
|
||
|
||
// 白名单检查(最先执行)
|
||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||
|
||
// 游戏模式切换(只对非白名单玩家)
|
||
if (ac.enforceGameMode != null) {
|
||
boolean exempt = isPlayerExemptFromModeChange(p, ac);
|
||
if (!exempt) {
|
||
GameMode target = GameMode.valueOf(ac.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
p.sendMessage("§e§l[区域防护] §f游戏模式已切换为: "
|
||
+ getChineseGameMode(target));
|
||
}
|
||
}
|
||
}
|
||
|
||
// 和平模式/袭击检查
|
||
if (ac.peaceMode) {
|
||
banHostilesWithWhitelist(p, ac);
|
||
} else if (ac.denyRaid) {
|
||
banRaidMobs(p, ac);
|
||
}
|
||
|
||
handleConfiscate(p, ac);
|
||
}
|
||
|
||
private boolean isPlayerExemptFromModeChange(
|
||
Player p, AreaConfig ac) {
|
||
// if (p.isOp()) return true;
|
||
Set<String> global = globalWhitelist.get("global");
|
||
if (global != null
|
||
&& global.contains(p.getName().toLowerCase())) {
|
||
return true;
|
||
}
|
||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||
if (areaWl != null
|
||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||
return true;
|
||
}
|
||
if (ac.modeExempt.contains(p.getName())) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private void banRangedHostiles(Player p, AreaConfig ac) {
|
||
for (Entity ent : p.getNearbyEntities(48, 48, 48)) {
|
||
if (isRangedHostile(ent.getType().name())) {
|
||
Location voidLoc = ent.getLocation().clone();
|
||
voidLoc.setY(ent.getWorld().getMinHeight() - 50);
|
||
ent.teleport(voidLoc);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 按配置清除效果
|
||
* denyRaid → 清除袭击相关
|
||
* denyAllEffects → 清除所有负面+中性
|
||
* denyAllEffects > denyRaid(全清优先)
|
||
*/
|
||
private void clearBadEffects(Player p, AreaConfig ac) {
|
||
// 和平模式 > 清除所有效果 > 禁止单个
|
||
if (ac.peaceMode || ac.clearAllBadEffects) {
|
||
PotionEffectType[] allBad = {
|
||
PotionEffectType.SLOWNESS,
|
||
PotionEffectType.MINING_FATIGUE,
|
||
PotionEffectType.INSTANT_DAMAGE,
|
||
PotionEffectType.NAUSEA,
|
||
PotionEffectType.BLINDNESS,
|
||
PotionEffectType.HUNGER,
|
||
PotionEffectType.WEAKNESS,
|
||
PotionEffectType.POISON,
|
||
PotionEffectType.WITHER,
|
||
PotionEffectType.LEVITATION,
|
||
PotionEffectType.UNLUCK,
|
||
PotionEffectType.DARKNESS,
|
||
PotionEffectType.WIND_CHARGED,
|
||
PotionEffectType.WEAVING,
|
||
PotionEffectType.OOZING,
|
||
PotionEffectType.INFESTED,
|
||
PotionEffectType.BAD_OMEN,
|
||
PotionEffectType.TRIAL_OMEN,
|
||
PotionEffectType.RAID_OMEN
|
||
};
|
||
for (PotionEffectType type : allBad) {
|
||
p.removePotionEffect(type);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 禁止袭击 → 自动关联清除
|
||
if (ac.denyRaid) {
|
||
p.removePotionEffect(PotionEffectType.BAD_OMEN);
|
||
p.removePotionEffect(PotionEffectType.RAID_OMEN);
|
||
}
|
||
|
||
// 配置文件单个清除
|
||
for (String effName : ac.clearEffects) {
|
||
PotionEffectType type = resolveEffectType(effName);
|
||
if (type != null) {
|
||
p.removePotionEffect(type);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
private PotionEffectType resolveEffectType(String name) {
|
||
if (name == null || name.isEmpty()) return null;
|
||
|
||
// 去掉"效果"后缀
|
||
String clean = name;
|
||
if (clean.endsWith("效果")) {
|
||
clean = clean.substring(0, clean.length() - 2);
|
||
}
|
||
|
||
// 硬编码中文名→英文ID映射(不依赖任何外部Map)
|
||
switch (clean) {
|
||
// 负面
|
||
case "缓慢":
|
||
return resolveEffectType("slowness");
|
||
case "挖掘疲劳":
|
||
return resolveEffectType("mining_fatigue");
|
||
case "瞬间伤害":
|
||
return resolveEffectType("instant_damage");
|
||
case "反胃":
|
||
return resolveEffectType("nausea");
|
||
case "失明":
|
||
return resolveEffectType("blindness");
|
||
case "饥饿":
|
||
return resolveEffectType("hunger");
|
||
case "虚弱":
|
||
return resolveEffectType("weakness");
|
||
case "中毒":
|
||
return resolveEffectType("poison");
|
||
case "凋零":
|
||
return resolveEffectType("wither");
|
||
case "飘浮":
|
||
return resolveEffectType("levitation");
|
||
case "霉运":
|
||
return resolveEffectType("unluck");
|
||
case "黑暗":
|
||
return resolveEffectType("darkness");
|
||
case "蓄风":
|
||
return resolveEffectType("wind_charged");
|
||
case "盘丝":
|
||
return resolveEffectType("weaving");
|
||
case "渗浆":
|
||
return resolveEffectType("oozing");
|
||
case "寄生":
|
||
return resolveEffectType("infested");
|
||
// 中性
|
||
case "不祥之兆":
|
||
case "不祥征兆":
|
||
return resolveEffectType("bad_omen");
|
||
case "袭击之兆":
|
||
case "袭击征兆":
|
||
return resolveEffectType("raid_omen");
|
||
case "试炼之兆":
|
||
case "试炼征兆":
|
||
return resolveEffectType("trial_omen");
|
||
// 正面
|
||
case "迅捷":
|
||
return resolveEffectType("speed");
|
||
case "急迫":
|
||
return resolveEffectType("haste");
|
||
case "力量":
|
||
return resolveEffectType("strength");
|
||
case "瞬间治疗":
|
||
return resolveEffectType("instant_health");
|
||
case "跳跃提升":
|
||
return resolveEffectType("jump_boost");
|
||
case "生命恢复":
|
||
return resolveEffectType("regeneration");
|
||
case "抗性提升":
|
||
return resolveEffectType("resistance");
|
||
case "抗火":
|
||
return resolveEffectType("fire_resistance");
|
||
case "水下呼吸":
|
||
return resolveEffectType("water_breathing");
|
||
case "隐身":
|
||
return resolveEffectType("invisibility");
|
||
case "夜视":
|
||
return resolveEffectType("night_vision");
|
||
case "发光":
|
||
return resolveEffectType("glowing");
|
||
case "生命提升":
|
||
return resolveEffectType("health_boost");
|
||
case "伤害吸收":
|
||
return resolveEffectType("absorption");
|
||
case "饱和":
|
||
return resolveEffectType("saturation");
|
||
case "幸运":
|
||
return resolveEffectType("luck");
|
||
case "村庄英雄":
|
||
return resolveEffectType("hero_of_the_village");
|
||
case "缓降":
|
||
return resolveEffectType("slow_falling");
|
||
case "潮涌能量":
|
||
return resolveEffectType("conduit_power");
|
||
case "海豚的恩惠":
|
||
return resolveEffectType("dolphins_grace");
|
||
case "致命中毒":
|
||
return resolveEffectType("poison");
|
||
default:
|
||
break;
|
||
}
|
||
|
||
|
||
// 兜底:直接用原版英文ID查(★ 注意是 PotionEffectType.getByName,不是 resolveEffectType)
|
||
PotionEffectType fallback = PotionEffectType.getByName(clean);
|
||
if (fallback != null) return fallback;
|
||
fallback = PotionEffectType.getByName(clean.toLowerCase());
|
||
if (fallback != null) return fallback;
|
||
|
||
plugin.getLogger().warning("[防护] 无法识别效果: " + name);
|
||
return null;
|
||
|
||
}
|
||
|
||
|
||
private static final Map<String, PotionEffectType> EFFECT_MAP
|
||
= new HashMap<>();
|
||
|
||
|
||
private void clearRaidEffects(Player p) {
|
||
boolean cleared = false;
|
||
try {
|
||
if (p.hasPotionEffect(PotionEffectType.BAD_OMEN)) {
|
||
p.removePotionEffect(PotionEffectType.BAD_OMEN);
|
||
cleared = true;
|
||
}
|
||
} catch (Exception ignored) {
|
||
}
|
||
try {
|
||
if (p.hasPotionEffect(PotionEffectType.RAID_OMEN)) {
|
||
p.removePotionEffect(PotionEffectType.RAID_OMEN);
|
||
cleared = true;
|
||
}
|
||
} catch (Exception ignored) {
|
||
}
|
||
if (cleared) {
|
||
p.sendMessage("§c§l[区域防护] §f已清除袭击相关效果");
|
||
}
|
||
}
|
||
|
||
public Map<UUID, List<PotionEffectType>>
|
||
getPendingClearEffects() {
|
||
return pendingClearEffects;
|
||
}
|
||
|
||
public void onPlayerOffline(UUID uid, String playerName,
|
||
String areaName) {
|
||
// 清除该玩家在该区域的所有效果
|
||
List<PotionEffectType> given = getPlayerEffects(uid, areaName);
|
||
for (PotionEffectType type : given) {
|
||
// 只清除我们给予的效果
|
||
Player p = Bukkit.getPlayer(uid);
|
||
if (p != null) {
|
||
p.removePotionEffect(type);
|
||
}
|
||
}
|
||
// 清除DB记录
|
||
removePlayerEffects(uid, areaName);
|
||
plugin.getLogger().info("[防护] " + playerName
|
||
+ " 下线,已清除" + given.size()
|
||
+ "个效果并清理DB");
|
||
}
|
||
|
||
@EventHandler
|
||
public void onTeleport(PlayerTeleportEvent e) {
|
||
Location from = e.getFrom();
|
||
Location to = e.getTo();
|
||
if (to == null) return;
|
||
|
||
if (from.getWorld() == to.getWorld()
|
||
&& from.distance(to) < 5) return;
|
||
|
||
Player p = e.getPlayer();
|
||
UUID uid = p.getUniqueId();
|
||
|
||
// 标记传送中
|
||
teleporting.add(uid);
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||
teleporting.remove(uid);
|
||
}, 5L);
|
||
|
||
AreaConfig fromArea = getArea(
|
||
from.getWorld().getName(),
|
||
from.getBlockX(), from.getBlockY(),
|
||
from.getBlockZ());
|
||
AreaConfig toArea = getArea(
|
||
to.getWorld().getName(),
|
||
to.getBlockX(), to.getBlockY(),
|
||
to.getBlockZ());
|
||
|
||
String fromName = fromArea != null
|
||
? fromArea.name : null;
|
||
String toName = toArea != null
|
||
? toArea.name : null;
|
||
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送] ★★★进入onTeleport: "
|
||
+ p.getName()
|
||
+ " [" + fromName + "] → [" + toName + "]"
|
||
+ " teleporting=" + teleporting.contains(uid));*/
|
||
|
||
if (Objects.equals(fromName, toName)) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送] 区域相同, 跳过");*/
|
||
return;
|
||
}
|
||
|
||
// ===== 离开旧区域 =====
|
||
if (fromName != null) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] ★★★开始离开处理: "
|
||
+ fromName);*/
|
||
|
||
// 收标
|
||
List<PotionEffectType> marked =
|
||
playerMarkedEffects.remove(uid);
|
||
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] ★★★收标结果: "
|
||
+ (marked != null ? marked.size() : "null"));*/
|
||
|
||
if (marked != null) {
|
||
for (PotionEffectType t : marked) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] 标记项: "
|
||
+ t.getName());*/
|
||
}
|
||
} else {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] ★★标记为null! "
|
||
+ "尝试其他key查找...");*/
|
||
// 遍历查找可能的标记
|
||
for (Map.Entry<UUID, List<PotionEffectType>> entry
|
||
: playerMarkedEffects.entrySet()) {
|
||
if (entry.getKey().equals(uid)) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] 找到标记! 效果数="
|
||
+ entry.getValue().size());*/
|
||
}
|
||
}
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] playerMarkedEffects大小="
|
||
+ playerMarkedEffects.size());*/
|
||
}
|
||
|
||
// 取消旧延时
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
pendingClearArea.remove(uid);
|
||
pendingClearEffects.remove(uid);
|
||
}
|
||
|
||
// 创建15秒延时清理
|
||
if (marked != null && !marked.isEmpty()) {
|
||
String clearArea = fromName;
|
||
UUID clearUid = uid;
|
||
List<PotionEffectType> toClear =
|
||
new ArrayList<>(marked);
|
||
pendingClearArea.put(uid, clearArea);
|
||
pendingClearEffects.put(uid, toClear);
|
||
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-离开] ★★★创建延时任务: "
|
||
+ toClear.size() + "个效果");*/
|
||
|
||
BukkitTask task = new BukkitRunnable() {
|
||
@Override
|
||
public void run() {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-清理] ★★★延时触发!");*/
|
||
pendingClearTask.remove(clearUid);
|
||
pendingClearArea.remove(clearUid);
|
||
|
||
Player online =
|
||
Bukkit.getPlayer(clearUid);
|
||
/*
|
||
plugin.getLogger().info(
|
||
"[防护-传送-清理] 在线="
|
||
+ (online != null && online.isOnline())
|
||
+ " 效果数=" + toClear.size()
|
||
+ " 区域=" + clearArea);*/
|
||
|
||
if (online == null
|
||
|| !online.isOnline()) {
|
||
plugin.getLogger().info(
|
||
"[防护-传送-清理] 玩家不在线, 清DB");
|
||
removePlayerEffects(
|
||
clearUid, clearArea);
|
||
return;
|
||
}
|
||
|
||
int cleared = 0;
|
||
for (PotionEffectType type : toClear) {
|
||
PotionEffect eff =
|
||
online.getPotionEffect(type);
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-清理] 检查: "
|
||
+ type.getName()
|
||
+ " 身上=" + (eff != null));*/
|
||
if (eff != null) {
|
||
online.removePotionEffect(type);
|
||
cleared++;
|
||
}
|
||
}
|
||
removePlayerEffects(clearUid, clearArea);
|
||
plugin.getLogger().info(
|
||
"[防护-传送-清理] ★★★完成: 清除"
|
||
+ cleared + "/" + toClear.size());
|
||
}
|
||
}.runTaskLater(plugin, 300L);
|
||
pendingClearTask.put(uid, task);
|
||
|
||
}
|
||
|
||
// 离开提示
|
||
if (fromArea != null && fromArea.leaveMsg != null
|
||
&& !fromArea.leaveMsg.isEmpty()) {
|
||
p.sendMessage(formatAreaMsg(fromArea.leaveMsg));
|
||
}
|
||
}
|
||
|
||
// ===== 进入新区域 =====
|
||
if (toName != null) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-进入] ★★★开始进入处理: "
|
||
+ toName);*/
|
||
|
||
// 取消旧延时
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
String oldP = pendingClearArea.remove(uid);
|
||
plugin.getLogger().info(
|
||
"[防护-传送-进入] 取消旧延时, 旧区域="
|
||
+ oldP);
|
||
if (oldP != null) {
|
||
diffEffects(p, uid, oldP, toName);
|
||
}
|
||
}
|
||
|
||
// 进入提示
|
||
if (toArea != null && toArea.enterMsg != null
|
||
&& !toArea.enterMsg.isEmpty()) {
|
||
p.sendMessage(formatAreaMsg(toArea.enterMsg));
|
||
}
|
||
|
||
// 发放效果
|
||
if (toArea != null) {
|
||
/* plugin.getLogger().info(
|
||
"[防护-传送-进入] 调用applyRegionEffects");*/
|
||
applyRegionEffects(p, uid, toName, toArea);
|
||
// 清除效果
|
||
clearBadEffects(p, toArea);
|
||
|
||
if (toArea.confiscateItems != null
|
||
&& !toArea.confiscateItems.isEmpty()
|
||
&& !isExemptFromConfiscation(
|
||
p, toName)) {
|
||
confiscateItems(p, toArea);
|
||
}
|
||
}
|
||
|
||
// 强制模式
|
||
if (toArea != null && toArea.enforceGameMode != null
|
||
&& !toArea.enforceGameMode.isEmpty()
|
||
&& !isExemptFromGameMode(p, toName)) {
|
||
GameMode target = GameMode.valueOf(
|
||
toArea.enforceGameMode);
|
||
if (p.getGameMode() != target) {
|
||
p.setGameMode(target);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新记录
|
||
if (toName != null) {
|
||
playerCurrentArea.put(uid, toName);
|
||
} else {
|
||
playerCurrentArea.remove(uid);
|
||
}
|
||
}
|
||
|
||
|
||
public Map<UUID, BukkitTask> getPendingClearTasks() {
|
||
return pendingClearTask;
|
||
}
|
||
|
||
public Map<UUID, List<PotionEffectType>>
|
||
getPlayerMarkedEffects() {
|
||
return playerMarkedEffects;
|
||
}
|
||
|
||
public void handleInside(Player p, AreaConfig ac) {
|
||
clearBadEffects(p, ac);
|
||
|
||
if (ac.peaceMode) {
|
||
banHostilesWithWhitelist(p, ac);
|
||
} else if (ac.denyRaid) {
|
||
banRaidMobs(p, ac);
|
||
}
|
||
|
||
// 全白或区白 → 跳过所有限制性规则(包括没收)
|
||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||
|
||
handleConfiscate(p, ac);
|
||
}
|
||
|
||
public void cleanupExpiredProtections() {
|
||
protectedEntities.entrySet().removeIf(
|
||
entry -> System.currentTimeMillis() > entry.getValue());
|
||
}
|
||
|
||
// 禁止袭击:只驱逐袭击怪物
|
||
private void banRaidMobs(Player p, AreaConfig ac) {
|
||
for (Entity ent : p.getNearbyEntities(32, 32, 32)) {
|
||
String t = ent.getType().name();
|
||
if (t.equals("PILLAGER")
|
||
|| t.equals("VINDICATOR")
|
||
|| t.equals("EVOKER")
|
||
|| t.equals("RAVAGER")
|
||
|| t.equals("VEX")
|
||
|| t.equals("WITCH")
|
||
|| t.equals("ILLUSIONER")
|
||
|| t.equals("ZOMBIE_VILLAGER")) {
|
||
Location voidLoc = ent.getLocation().clone();
|
||
voidLoc.setY(voidLoc.getWorld().getMinHeight() - 50);
|
||
ent.teleport(voidLoc);
|
||
}
|
||
}
|
||
}
|
||
|
||
public Set<String> getAreaNames() {
|
||
return areas.keySet();
|
||
}
|
||
|
||
public Collection<BukkitTask> getPendingTasks() {
|
||
return pendingClearTask.values();
|
||
}
|
||
|
||
|
||
// 判断是否为远程攻击敌对生物
|
||
private boolean isRangedHostile(String typeName) {
|
||
switch (typeName) {
|
||
// ===== 骷髅系 =====
|
||
case "SKELETON": // 骷髅(弓箭)
|
||
case "STRAY": // 流浪者(迟缓之箭)
|
||
case "BOGGED": // 虫蚀骷髅(毒箭)
|
||
case "WITHER_SKELETON": // 凋灵骷髅(近战但算灾厄)
|
||
|
||
// ===== 灾厄村民 =====
|
||
case "PILLAGER": // 掠夺者(弩)
|
||
case "ILLUSIONER": // 幻术师(弓箭+失明)
|
||
case "EVOKER": // 幻魔者(唤魔)
|
||
case "VINDICATOR": // 卫道士(斧头近战)
|
||
case "VEX": // 恼鬼(剑近战,但会穿墙飞行)
|
||
|
||
// ===== 法术/投射 =====
|
||
case "BLAZE": // 烈焰人(火球)
|
||
case "WITCH": // 女巫(药水)
|
||
|
||
// ===== 飞行/爆炸 =====
|
||
case "GHAST": // 恶魂(火球)
|
||
case "PHANTOM": // 幻翼(俯冲)
|
||
case "BIG_GHAST": // 大恶魂(1.21新增)
|
||
|
||
// ===== 水下 =====
|
||
case "GUARDIAN": // 守卫者(激光)
|
||
case "ELDER_GUARDIAN": // 远古守卫者(激光)
|
||
|
||
// ===== 远程特殊 =====
|
||
case "SHULKER": // 潜影贝(潜影弹)
|
||
case "BREEZE": // 旋风人(风弹)
|
||
|
||
// ===== 蜘蛛系(远程毒液攻击) =====
|
||
case "SPIDER": // 蜘蛛(白天远程吐丝)
|
||
case "CAVE_SPIDER": // 洞穴蜘蛛(毒)
|
||
|
||
// ===== 1.21新增 =====
|
||
case "WIND_CHARGED": // 蓄风怪(风弹)
|
||
case "WEAVING": // 盘丝怪(吐丝)
|
||
case "OOZING": // 渗浆怪(史莱姆)
|
||
case "INFESTED": // 寄生怪(蠹虫)
|
||
|
||
// ===== 盔甲/变种(部分版本) =====
|
||
case "ARMORED_SKELETON": // 盔甲骷髅
|
||
case "ARCHER_SKELETON": // 弓箭骷髅(部分版本)
|
||
case "SNOW_GOLEM": // 雪傀儡(不是敌对但远程)
|
||
|
||
// ===== 亡灵骑乘 =====
|
||
case "HUSK": // 尸壳(近战但属于灾厄系)
|
||
case "DROWNED": // 溺尸(三叉戟远程)
|
||
case "ZOMBIE_VILLAGER": // 僵尸村民
|
||
case "ZOMBIE_PIGMAN": // 僵尸猪灵
|
||
case "ZOMBIFIED_PIGLIN": // 僵尸猪灵(1.16+)
|
||
case "PIGLIN_BRUTE": // 猪灵蛮兵
|
||
|
||
// ===== 猪灵(弓箭) =====
|
||
case "PIGLIN": // 猪灵(金弩远程)
|
||
|
||
// ===== 末影系 =====
|
||
case "ENDER_DRAGON": // 末影龙(远程吐息)
|
||
case "WARDEN": // 监守者(远程音波)
|
||
|
||
// ===== 其他 =====
|
||
case "CREPER": // 苦力怕(爆炸不算远程但危险)
|
||
case "CREEPER": // 苦力怕
|
||
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void applyEffects(Player p, AreaConfig ac) {
|
||
for (String[] parts : ac.giveEffects) {
|
||
String effName = parts[0];
|
||
int level = 1;
|
||
int duration = 999;
|
||
try { level = Integer.parseInt(parts[1]); }
|
||
catch (NumberFormatException ignored) {}
|
||
try { duration = Integer.parseInt(parts[2]); }
|
||
catch (NumberFormatException ignored) {}
|
||
|
||
PotionEffectType type = resolveEffectType(effName);
|
||
plugin.getLogger().info("[防护调试] 给予: "
|
||
+ effName + " -> " + type);
|
||
if (type != null) {
|
||
p.addPotionEffect(new PotionEffect(
|
||
type, duration * 20, level - 1));
|
||
plugin.getLogger().info("[防护调试] 已给予: "
|
||
+ effName + " 等级" + level
|
||
+ " 持续" + duration + "秒");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onBlockPlace(BlockPlaceEvent e) {
|
||
Player p = e.getPlayer();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
e.getBlock().getX(),
|
||
e.getBlock().getY(),
|
||
e.getBlock().getZ());
|
||
if (ac != null && ac.denyBlockPlace
|
||
&& !isPlayerWhitelisted(p.getName(), ac)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止放置方块");
|
||
}
|
||
}
|
||
|
||
@EventHandler
|
||
public void onBlockBreak(BlockBreakEvent e) {
|
||
Player p = e.getPlayer();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
e.getBlock().getX(),
|
||
e.getBlock().getY(),
|
||
e.getBlock().getZ());
|
||
if (ac != null && ac.denyBlockBreak
|
||
&& !isPlayerWhitelisted(p.getName(), ac)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止破坏方块");
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onDamage(EntityDamageEvent e) {
|
||
if (!(e.getEntity() instanceof Player)) return;
|
||
Player p = (Player) e.getEntity();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) return;
|
||
// 保护性规则:对所有人无条件生效
|
||
if (ac.denyAllDamage) { e.setCancelled(true); return; }
|
||
if (ac.denyFallDamage
|
||
&& e.getCause() == EntityDamageEvent.DamageCause.FALL)
|
||
e.setCancelled(true);
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onPVP(EntityDamageByEntityEvent e) {
|
||
if (!(e.getDamager() instanceof Player)) return;
|
||
if (!(e.getEntity() instanceof Player)) return;
|
||
Player p = (Player) e.getEntity();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac != null && ac.denyPVP
|
||
&& !isPlayerWhitelisted(p.getName(), ac)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止PVP");
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onFood(FoodLevelChangeEvent e) {
|
||
if (!(e.getEntity() instanceof Player)) return;
|
||
Player p = (Player) e.getEntity();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac != null && ac.denyHunger)
|
||
e.setCancelled(true);
|
||
}
|
||
|
||
|
||
|
||
@EventHandler
|
||
public void onDrop(PlayerDropItemEvent e) {
|
||
Player p = e.getPlayer();
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac != null && ac.denyDrop
|
||
&& !isPlayerWhitelisted(p.getName(), ac)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止丢弃物品");
|
||
}
|
||
}
|
||
|
||
// 替换整个 onInteract 方法
|
||
@EventHandler
|
||
public void onInteract(PlayerInteractEvent e) {
|
||
Player p = e.getPlayer();
|
||
ItemStack hand = p.getInventory().getItemInMainHand();
|
||
if (hand == null) return;
|
||
|
||
// ===== 选区工具 =====
|
||
if (hand.getType() == Material.BREEZE_ROD) {
|
||
ItemMeta meta = hand.getItemMeta();
|
||
if (meta != null && meta.getDisplayName() != null
|
||
&& meta.getDisplayName().contains("区域选择")) {
|
||
Action action = e.getAction();
|
||
boolean isLeft = (action == Action.LEFT_CLICK_AIR
|
||
|| action == Action.LEFT_CLICK_BLOCK);
|
||
boolean isRight = (action == Action.RIGHT_CLICK_AIR
|
||
|| action == Action.RIGHT_CLICK_BLOCK);
|
||
if (isLeft || isRight) {
|
||
e.setCancelled(true);
|
||
Block block = e.getClickedBlock();
|
||
if (block == null) {
|
||
try { block = p.getTargetBlockExact(5); }
|
||
catch (Exception ignored) {}
|
||
}
|
||
if (block == null) return;
|
||
UUID uid = p.getUniqueId();
|
||
if (isLeft) {
|
||
pos1.put(uid, block.getLocation());
|
||
p.sendMessage("§a§l[防护] §f位置1: "
|
||
+ block.getX() + ", "
|
||
+ block.getY() + ", "
|
||
+ block.getZ());
|
||
} else {
|
||
pos2.put(uid, block.getLocation());
|
||
p.sendMessage("§a§l[防护] §f位置2: "
|
||
+ block.getX() + ", "
|
||
+ block.getY() + ", "
|
||
+ block.getZ());
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// ===== 放置实体类物品时不检查(展示框、矿车、船、地图等)=====
|
||
Material mat = hand.getType();
|
||
if (mat == Material.ITEM_FRAME
|
||
|| mat == Material.GLOW_ITEM_FRAME
|
||
|| mat == Material.MINECART
|
||
|| mat == Material.TNT_MINECART
|
||
|| mat == Material.CHEST_MINECART
|
||
|| mat == Material.HOPPER_MINECART
|
||
|| mat == Material.OAK_BOAT
|
||
|| mat == Material.SPRUCE_BOAT
|
||
|| mat == Material.BIRCH_BOAT
|
||
|| mat == Material.JUNGLE_BOAT
|
||
|| mat == Material.ACACIA_BOAT
|
||
|| mat == Material.DARK_OAK_BOAT
|
||
|| mat == Material.MANGROVE_BOAT
|
||
|| mat == Material.CHERRY_BOAT
|
||
|| mat == Material.BAMBOO_RAFT
|
||
|| mat == Material.MAP
|
||
|| mat == Material.ARMOR_STAND) {
|
||
return;
|
||
}
|
||
|
||
// ===== 区域规则检查 =====
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) return;
|
||
|
||
// 末影珍珠禁止
|
||
if (ac.denyEnderPearl
|
||
&& hand.getType() == Material.ENDER_PEARL) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠");
|
||
return;
|
||
}
|
||
|
||
// 弓箭禁止
|
||
if (ac.denyBow
|
||
&& (hand.getType() == Material.BOW
|
||
|| hand.getType() == Material.CROSSBOW)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止使用弓箭");
|
||
return;
|
||
}
|
||
|
||
// 白名单跳过
|
||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||
|
||
// 禁止使用物品
|
||
String typeId = hand.getType().name();
|
||
if (ac.denyUseItems.contains(typeId)) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止使用此物品");
|
||
return;
|
||
}
|
||
|
||
// 没收检查
|
||
handleConfiscate(p, ac);
|
||
}
|
||
|
||
|
||
|
||
@EventHandler
|
||
public void onExplosion(EntityExplodeEvent e) {
|
||
Iterator<Block> it = e.blockList().iterator();
|
||
while (it.hasNext()) {
|
||
Block b = it.next();
|
||
AreaConfig ac = getArea(
|
||
b.getWorld().getName(),
|
||
b.getX(), b.getY(), b.getZ());
|
||
if (ac != null && ac.denyExplosion)
|
||
it.remove();
|
||
}
|
||
}
|
||
public void loadWhitelists() {
|
||
globalPlayerWhitelist.clear();
|
||
areaPlayerWhitelist.clear();
|
||
|
||
if (!whitelistDir.exists()) {
|
||
whitelistDir.mkdirs();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 加载全局白名单
|
||
if (globalWhitelistFile.exists()) {
|
||
List<String> lines =
|
||
java.nio.file.Files.readAllLines(
|
||
globalWhitelistFile.toPath(),
|
||
StandardCharsets.UTF_8);
|
||
for (String line : lines) {
|
||
String name = line.trim();
|
||
if (!name.isEmpty()
|
||
&& !name.startsWith("#")) {
|
||
globalPlayerWhitelist.add(name);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 加载各区域白名单
|
||
File[] files = whitelistDir.listFiles(
|
||
(File d, String n) -> n.endsWith(".txt")
|
||
&& !n.equals("全局白名单.txt"));
|
||
if (files != null) {
|
||
for (File f : files) {
|
||
String areaName = f.getName()
|
||
.replace(".txt", "");
|
||
Set<String> set =
|
||
ConcurrentHashMap.newKeySet();
|
||
List<String> lines =
|
||
java.nio.file.Files.readAllLines(
|
||
f.toPath(),
|
||
StandardCharsets.UTF_8);
|
||
for (String line : lines) {
|
||
String name = line.trim();
|
||
if (!name.isEmpty()
|
||
&& !name.startsWith("#")) {
|
||
set.add(name);
|
||
}
|
||
}
|
||
if (!set.isEmpty()) {
|
||
areaPlayerWhitelist.put(
|
||
areaName, set);
|
||
}
|
||
}
|
||
}
|
||
|
||
plugin.getLogger().info(
|
||
"[防护] 白名单加载完成,全局: "
|
||
+ globalPlayerWhitelist.size()
|
||
+ ",区域数: "
|
||
+ areaPlayerWhitelist.size());
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 加载白名单失败: "
|
||
+ e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onPotionSplash(PotionSplashEvent e) {
|
||
if (!(e.getEntity().getShooter() instanceof Player)) return;
|
||
Player shooter = (Player) e.getEntity().getShooter();
|
||
AreaConfig ac = getArea(
|
||
shooter.getWorld().getName(),
|
||
shooter.getLocation().getBlockX(),
|
||
shooter.getLocation().getBlockY(),
|
||
shooter.getLocation().getBlockZ());
|
||
if (ac != null && ac.denyPotion
|
||
&& !isPlayerWhitelisted(shooter.getName(), ac)) {
|
||
e.setCancelled(true);
|
||
shooter.sendMessage("§c§l[区域防护] §f禁止使用药水");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析区域名:先精准匹配,再模糊匹配
|
||
* @param input 用户输入的区域名
|
||
* @return 匹配到的区域名,无匹配返回null
|
||
*/
|
||
public String resolveAreaName(String input) {
|
||
if (input == null || input.isEmpty()) return null;
|
||
if (input.equalsIgnoreCase("global")) return "global";
|
||
|
||
// 第一步:精准匹配
|
||
for (String name : areas.keySet()) {
|
||
if (name.equalsIgnoreCase(input)) {
|
||
return name;
|
||
}
|
||
}
|
||
|
||
// 第二步:包含匹配(用户只打了部分名字)
|
||
for (String name : areas.keySet()) {
|
||
if (name.contains(input) || input.contains(name)) {
|
||
return name;
|
||
}
|
||
}
|
||
|
||
// 第三步:编辑距离模糊匹配(著称→主城)
|
||
String bestMatch = null;
|
||
int bestDistance = Integer.MAX_VALUE;
|
||
for (String name : areas.keySet()) {
|
||
int dist = levenshteinDistance(
|
||
input.toLowerCase(), name.toLowerCase());
|
||
if (dist < bestDistance) {
|
||
bestDistance = dist;
|
||
bestMatch = name;
|
||
}
|
||
}
|
||
|
||
// 容错阈值:编辑距离 <= 名字长度的一半
|
||
if (bestMatch != null) {
|
||
int threshold = Math.max(1, bestMatch.length() / 2);
|
||
if (bestDistance <= threshold) {
|
||
return bestMatch;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 判断字符串是否是区域名(精准或模糊)
|
||
*/
|
||
private boolean isAreaName(String name) {
|
||
if (name.equalsIgnoreCase("global")) return false;
|
||
return resolveAreaName(name) != null;
|
||
}
|
||
|
||
/**
|
||
* 获取模糊匹配的实际区域名(用于提示用户)
|
||
*/
|
||
private String getResolvedName(String input) {
|
||
return resolveAreaName(input);
|
||
}
|
||
|
||
/**
|
||
* 编辑距离算法(莱文斯坦距离)
|
||
*/
|
||
private int levenshteinDistance(String s1, String s2) {
|
||
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
|
||
for (int i = 0; i <= s1.length(); i++) dp[i][0] = i;
|
||
for (int j = 0; j <= s2.length(); j++) dp[0][j] = j;
|
||
for (int i = 1; i <= s1.length(); i++) {
|
||
for (int j = 1; j <= s2.length(); j++) {
|
||
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
|
||
dp[i][j] = dp[i - 1][j - 1];
|
||
} else {
|
||
dp[i][j] = 1 + Math.min(dp[i - 1][j - 1],
|
||
Math.min(dp[i - 1][j], dp[i][j - 1]));
|
||
}
|
||
}
|
||
}
|
||
return dp[s1.length()][s2.length()];
|
||
}
|
||
|
||
/**
|
||
* 智能重排参数:找出子命令位置,重排为标准顺序
|
||
* 标准顺序:/protect 子命令 参数1 参数2 ...
|
||
*/
|
||
private String[] smartReorderArgs(String[] original) {
|
||
if (original.length <= 1) return original;
|
||
|
||
Set<String> knownSubs = new HashSet<>(Arrays.asList(
|
||
"add", "remove", "additem", "removeitem",
|
||
"addname", "removename", "listname",
|
||
"addwhite", "removewhite", "listwhite",
|
||
"list", "listitem", "创建", "删除", "重载",
|
||
"工具", "expand", "contraction",
|
||
"on", "off", "tempon", "modeexempt"
|
||
));
|
||
|
||
String first = original[0].toLowerCase();
|
||
if (knownSubs.contains(first)) {
|
||
return original;
|
||
}
|
||
|
||
int subIndex = -1;
|
||
for (int i = 0; i < original.length; i++) {
|
||
if (knownSubs.contains(original[i].toLowerCase())) {
|
||
subIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (subIndex < 0) {
|
||
return original;
|
||
}
|
||
|
||
String[] reordered = new String[original.length];
|
||
reordered[0] = original[subIndex];
|
||
int idx = 1;
|
||
for (int i = 0; i < original.length; i++) {
|
||
if (i == subIndex) continue;
|
||
reordered[idx] = original[i];
|
||
idx++;
|
||
}
|
||
|
||
return reordered;
|
||
}
|
||
|
||
// ==================== 管理命令 ====================
|
||
public boolean handleCommand(CommandSender sender,
|
||
String[] args) {
|
||
args = smartReorderArgs(args);
|
||
if (args.length == 0) {
|
||
showHelp(sender);
|
||
return true;
|
||
}
|
||
String sub = args[0].toLowerCase();
|
||
// ===== 诊断命令 =====
|
||
if (sub.equals("sdf1debug")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
UUID uid = p.getUniqueId();
|
||
|
||
sender.sendMessage("§e===== 调试信息 =====");
|
||
sender.sendMessage("§7当前区域: "
|
||
+ playerCurrentArea.get(uid));
|
||
sender.sendMessage("§7标记效果数: "
|
||
+ (playerMarkedEffects.containsKey(uid)
|
||
? playerMarkedEffects.get(uid).size()
|
||
: "无标记"));
|
||
sender.sendMessage("§7待清理任务: "
|
||
+ pendingClearTask.containsKey(uid));
|
||
sender.sendMessage("§7区域数: " + areas.size());
|
||
|
||
if (playerMarkedEffects.containsKey(uid)) {
|
||
List<PotionEffectType> marked =
|
||
playerMarkedEffects.get(uid);
|
||
for (PotionEffectType type : marked) {
|
||
sender.sendMessage("§7 标记: "
|
||
+ type.getName()
|
||
+ " 身上有="
|
||
+ (p.getPotionEffect(type) != null));
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== 强制清理命令(测试用)=====
|
||
if (sub.equals("testclear")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
UUID uid = p.getUniqueId();
|
||
|
||
List<PotionEffectType> marked =
|
||
playerMarkedEffects.remove(uid);
|
||
if (marked != null) {
|
||
for (PotionEffectType type : marked) {
|
||
p.removePotionEffect(type);
|
||
}
|
||
sender.sendMessage("§a已强制清理 "
|
||
+ marked.size() + " 个效果");
|
||
} else {
|
||
sender.sendMessage("§c无标记效果可清理");
|
||
}
|
||
|
||
// 取消延时任务
|
||
if (pendingClearTask.containsKey(uid)) {
|
||
pendingClearTask.get(uid).cancel();
|
||
pendingClearTask.remove(uid);
|
||
pendingClearArea.remove(uid);
|
||
pendingClearEffects.remove(uid);
|
||
sender.sendMessage("§a已取消延时任务");
|
||
}
|
||
|
||
removePlayerEffects(uid,
|
||
playerCurrentArea.getOrDefault(uid, ""));
|
||
sender.sendMessage("§aDB已清理");
|
||
return true;
|
||
}
|
||
|
||
// ===== 工具 =====
|
||
if (sub.equals("工具") || sub.equals("wand")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
ItemStack tool = new ItemStack(WAND);
|
||
ItemMeta meta = tool.getItemMeta();
|
||
meta.setDisplayName("§a§l区域选择工具");
|
||
meta.setLore(Arrays.asList(
|
||
"§7左键: 位置1", "§7右键: 位置2"));
|
||
tool.setItemMeta(meta);
|
||
p.getInventory().addItem(tool);
|
||
p.sendMessage("§a§l[防护] §f已获取选择工具");
|
||
return true;
|
||
}
|
||
|
||
// ===== 创建 =====
|
||
if (sub.equals("创建") || sub.equals("create")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§e用法: /protect 创建 <名>");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
UUID u = p.getUniqueId();
|
||
if (!pos1.containsKey(u)
|
||
|| !pos2.containsKey(u)) {
|
||
p.sendMessage("§c请先用工具选点");
|
||
return true;
|
||
}
|
||
String areaName = args[1];
|
||
File f = new File(rootDir, areaName + ".txt");
|
||
if (f.exists()) {
|
||
p.sendMessage("§c区域已存在");
|
||
return true;
|
||
}
|
||
Location l1 = pos1.get(u);
|
||
Location l2 = pos2.get(u);
|
||
try {
|
||
PrintWriter pw = new PrintWriter(
|
||
new OutputStreamWriter(
|
||
new FileOutputStream(f),
|
||
StandardCharsets.UTF_8));
|
||
pw.println("# 区域: " + areaName);
|
||
pw.println("起点: " + l1.getWorld().getName()
|
||
+ "," + l1.getBlockX()
|
||
+ "," + l1.getBlockZ());
|
||
pw.println("终点: " + l2.getWorld().getName()
|
||
+ "," + l2.getBlockX()
|
||
+ "," + l2.getBlockZ());
|
||
pw.println("高度范围: 0-255");
|
||
pw.println();
|
||
pw.println("# 禁止放置方块");
|
||
pw.println("# 禁止破坏方块");
|
||
pw.println("# 禁止PVP");
|
||
pw.println("# 效果: 夜视 1 999");
|
||
pw.println("# 进入提示: 欢迎来到保护区");
|
||
pw.println("# 离开提示: 已离开保护区");
|
||
pw.close();
|
||
p.sendMessage("§a§l[防护] §f区域 "
|
||
+ areaName + " 已创建");
|
||
loadAllAreas();
|
||
} catch (IOException ex) {
|
||
p.sendMessage("§c创建失败");
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== 和平白名单 =====
|
||
if (sub.equals("addname")) {
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别: "
|
||
+ args[1] + " 或 " + args[2]);
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(parsed[0]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
ac.peaceWhitelist.add(parsed[1]);
|
||
saveAreaConfig(ac);
|
||
sender.sendMessage("§a已添加: "
|
||
+ parsed[1] + " → "
|
||
+ parsed[0] + " 和平白名单");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect addname "
|
||
+ "<区域> <名字>");
|
||
return true;
|
||
}
|
||
if (sub.equals("removename")) {
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别");
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(parsed[0]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
ac.peaceWhitelist.remove(parsed[1]);
|
||
saveAreaConfig(ac);
|
||
sender.sendMessage("§a已移除: "
|
||
+ parsed[1] + " ← " + parsed[0]);
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect removename "
|
||
+ "<区域> <名字>");
|
||
return true;
|
||
}
|
||
if (sub.equals("listname")) {
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§c用法: /protect listname <区域>");
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(args[1]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§a" + args[1]
|
||
+ " 和平白名单:");
|
||
if (ac.peaceWhitelist.isEmpty()) {
|
||
sender.sendMessage("§7 (空)");
|
||
} else {
|
||
for (String n : ac.peaceWhitelist) {
|
||
sender.sendMessage("§7 - " + n);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== 模式排除 =====
|
||
if (sub.equals("addwhite")) {
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别");
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(parsed[0]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
ac.modeExempt.add(parsed[1]);
|
||
saveAreaConfig(ac);
|
||
sender.sendMessage("§a已添加: "
|
||
+ parsed[1] + " → "
|
||
+ parsed[0] + " 模式排除");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect addwhite "
|
||
+ "<区域> <玩家>");
|
||
return true;
|
||
}
|
||
if (sub.equals("removewhite")) {
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别");
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(parsed[0]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
ac.modeExempt.remove(parsed[1]);
|
||
saveAreaConfig(ac);
|
||
sender.sendMessage("§a已移除: "
|
||
+ parsed[1] + " ← "
|
||
+ parsed[0] + " 模式排除");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect removewhite "
|
||
+ "<区域> <玩家>");
|
||
return true;
|
||
}
|
||
if (sub.equals("listwhite")) {
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§c用法: /protect listwhite <区域>");
|
||
return true;
|
||
}
|
||
AreaConfig ac = areas.get(args[1]);
|
||
if (ac == null) {
|
||
sender.sendMessage("§c区域不存在");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§a" + args[1]
|
||
+ " 模式排除名单:");
|
||
if (ac.modeExempt.isEmpty()) {
|
||
sender.sendMessage("§7 (空)");
|
||
} else {
|
||
for (String n : ac.modeExempt) {
|
||
sender.sendMessage("§7 - " + n);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== 列表 =====
|
||
if (sub.equals("列表") || sub.equals("list")) {
|
||
if (args.length == 2) {
|
||
if (args[1].equalsIgnoreCase("global")) {
|
||
sender.sendMessage("§a全局白名单:");
|
||
if (globalPlayerWhitelist.isEmpty()) {
|
||
sender.sendMessage("§7 (空)");
|
||
} else {
|
||
for (String n
|
||
: globalPlayerWhitelist) {
|
||
sender.sendMessage("§7 - " + n);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
String an = args[1];
|
||
Set<String> wl =
|
||
areaPlayerWhitelist.get(an);
|
||
sender.sendMessage("§a" + an + " 白名单:");
|
||
if (wl == null || wl.isEmpty()) {
|
||
sender.sendMessage("§7 (空)");
|
||
} else {
|
||
for (String n : wl) {
|
||
sender.sendMessage("§7 - " + n);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
if (areas.isEmpty()) {
|
||
sender.sendMessage("§7暂无区域");
|
||
} else {
|
||
sender.sendMessage("§e§l区域列表:");
|
||
for (Map.Entry<String, AreaConfig> en
|
||
: areas.entrySet()) {
|
||
sender.sendMessage("§a " + en.getKey()
|
||
+ " §7规则:"
|
||
+ en.getValue().ruleCount()
|
||
+ "条");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== 重载 =====
|
||
if (sub.equals("重载") || sub.equals("reload")) {
|
||
reload();
|
||
sender.sendMessage("§a已重载 "
|
||
+ areas.size() + " 个区域");
|
||
return true;
|
||
}
|
||
|
||
// ===== 物品黑名单 =====
|
||
if (sub.equals("additem")) {
|
||
if (args.length == 2) {
|
||
String itemName = args[1].toUpperCase();
|
||
if (Material.matchMaterial(itemName)
|
||
== null) {
|
||
sender.sendMessage(
|
||
"§c无效物品ID: " + args[1]);
|
||
return true;
|
||
}
|
||
globalItemBlacklist.add(itemName);
|
||
sender.sendMessage("§a已全局加黑: "
|
||
+ itemName);
|
||
return true;
|
||
}
|
||
if (args.length == 3) {
|
||
String r1 = resolveAreaName(args[1]);
|
||
String r2 = resolveAreaName(args[2]);
|
||
String areaName;
|
||
String itemName;
|
||
if (r1 != null) {
|
||
areaName = r1;
|
||
itemName = args[2].toUpperCase();
|
||
} else if (r2 != null) {
|
||
areaName = r2;
|
||
itemName = args[1].toUpperCase();
|
||
} else {
|
||
areaName = "global";
|
||
itemName = args[1].toUpperCase();
|
||
}
|
||
if (Material.matchMaterial(itemName)
|
||
== null) {
|
||
sender.sendMessage(
|
||
"§c无效物品ID: " + itemName);
|
||
return true;
|
||
}
|
||
if (areaName.equalsIgnoreCase("global")) {
|
||
globalItemBlacklist.add(itemName);
|
||
sender.sendMessage("§a已全局加黑: "
|
||
+ itemName);
|
||
} else {
|
||
areaItemBlacklist
|
||
.computeIfAbsent(areaName,
|
||
k -> ConcurrentHashMap
|
||
.newKeySet())
|
||
.add(itemName);
|
||
sender.sendMessage("§a已加黑: "
|
||
+ itemName + " → " + areaName);
|
||
}
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect additem "
|
||
+ "<区域/物品> <区域/物品>");
|
||
return true;
|
||
}
|
||
if (sub.equals("removeitem")) {
|
||
if (args.length == 2) {
|
||
String itemName = args[1].toUpperCase();
|
||
if (!globalItemBlacklist
|
||
.contains(itemName)) {
|
||
sender.sendMessage(
|
||
"§c全局黑名单中没有: "
|
||
+ itemName);
|
||
return true;
|
||
}
|
||
globalItemBlacklist.remove(itemName);
|
||
sender.sendMessage("§a已移除: " + itemName);
|
||
return true;
|
||
}
|
||
if (args.length == 3) {
|
||
String r1 = resolveAreaName(args[1]);
|
||
String r2 = resolveAreaName(args[2]);
|
||
String areaName;
|
||
String itemName;
|
||
if (r1 != null) {
|
||
areaName = r1;
|
||
itemName = args[2].toUpperCase();
|
||
} else if (r2 != null) {
|
||
areaName = r2;
|
||
itemName = args[1].toUpperCase();
|
||
} else {
|
||
areaName = "global";
|
||
itemName = args[1].toUpperCase();
|
||
}
|
||
if (areaName.equalsIgnoreCase("global")) {
|
||
globalItemBlacklist.remove(itemName);
|
||
} else {
|
||
Set<String> list =
|
||
areaItemBlacklist.get(areaName);
|
||
if (list != null) {
|
||
list.remove(itemName);
|
||
}
|
||
}
|
||
sender.sendMessage("§a已移除: "
|
||
+ itemName);
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect removeitem "
|
||
+ "<区域/物品> <区域/物品>");
|
||
return true;
|
||
}
|
||
if (sub.equals("listitem")) {
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§a全局物品黑名单:");
|
||
if (globalItemBlacklist.isEmpty()) {
|
||
sender.sendMessage("§7(空)");
|
||
} else {
|
||
for (String item
|
||
: globalItemBlacklist) {
|
||
sender.sendMessage("§f- " + item);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
Set<String> list =
|
||
areaItemBlacklist.get(args[1]);
|
||
sender.sendMessage("§a" + args[1]
|
||
+ " 物品黑名单:");
|
||
if (list == null || list.isEmpty()) {
|
||
sender.sendMessage("§7(空)");
|
||
} else {
|
||
for (String item : list) {
|
||
sender.sendMessage("§f- " + item);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== expand 扩建 =====
|
||
if (sub.equals("expand")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) {
|
||
p.sendMessage("§c你不在任何防护区域内");
|
||
return true;
|
||
}
|
||
int amount = 5;
|
||
if (args.length >= 2) {
|
||
try {
|
||
amount = Integer.parseInt(args[1]);
|
||
} catch (NumberFormatException ignored) {}
|
||
}
|
||
int minX = Math.min(ac.x1, ac.x2);
|
||
int maxX = Math.max(ac.x1, ac.x2);
|
||
int minZ = Math.min(ac.z1, ac.z2);
|
||
int maxZ = Math.max(ac.z1, ac.z2);
|
||
float yaw = p.getLocation().getYaw();
|
||
String dir = yawToDir(yaw);
|
||
if (dir.equals("北")) minZ -= amount;
|
||
else if (dir.equals("南")) maxZ += amount;
|
||
else if (dir.equals("东")) maxX += amount;
|
||
else if (dir.equals("西")) minX -= amount;
|
||
ac.x1 = minX;
|
||
ac.x2 = maxX;
|
||
ac.z1 = minZ;
|
||
ac.z2 = maxZ;
|
||
saveAreaConfig(ac);
|
||
p.sendMessage("§a§l[防护] §f区域 §e" + ac.name
|
||
+ " §f向 §e" + dir + " §f扩建 §e"
|
||
+ amount + "§f格");
|
||
return true;
|
||
}
|
||
|
||
// ===== contract 收缩 =====
|
||
if (sub.equals("contraction")
|
||
|| sub.equals("contract")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) {
|
||
p.sendMessage("§c你不在任何防护区域内");
|
||
return true;
|
||
}
|
||
int amount = 5;
|
||
if (args.length >= 2) {
|
||
try {
|
||
amount = Integer.parseInt(args[1]);
|
||
} catch (NumberFormatException ignored) {}
|
||
}
|
||
int minX = Math.min(ac.x1, ac.x2);
|
||
int maxX = Math.max(ac.x1, ac.x2);
|
||
int minZ = Math.min(ac.z1, ac.z2);
|
||
int maxZ = Math.max(ac.z1, ac.z2);
|
||
float yaw = p.getLocation().getYaw();
|
||
String dir = yawToDir(yaw);
|
||
if (dir.equals("北")) minZ += amount;
|
||
else if (dir.equals("南")) maxZ -= amount;
|
||
else if (dir.equals("东")) maxX -= amount;
|
||
else if (dir.equals("西")) minX += amount;
|
||
if (minX > maxX || minZ > maxZ) {
|
||
p.sendMessage("§c收缩过度");
|
||
return true;
|
||
}
|
||
ac.x1 = minX;
|
||
ac.x2 = maxX;
|
||
ac.z1 = minZ;
|
||
ac.z2 = maxZ;
|
||
saveAreaConfig(ac);
|
||
p.sendMessage("§a§l[防护] §f区域 §e" + ac.name
|
||
+ " §f向 §e" + dir + " §f收缩 §e"
|
||
+ amount + "§f格");
|
||
return true;
|
||
}
|
||
|
||
// ===== 删除 =====
|
||
if (sub.equals("删除") || sub.equals("delete")) {
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§e用法: /protect 删除 <名>");
|
||
return true;
|
||
}
|
||
File f = new File(rootDir, args[1] + ".txt");
|
||
if (f.exists()) {
|
||
f.delete();
|
||
areas.remove(args[1]);
|
||
sender.sendMessage("§a已删除: " + args[1]);
|
||
} else {
|
||
sender.sendMessage("§c不存在: " + args[1]);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ===== add 加白名单 =====
|
||
if (sub.equals("add")) {
|
||
if (args.length == 2) {
|
||
String name = args[1];
|
||
if (globalPlayerWhitelist.contains(name)) {
|
||
sender.sendMessage("§e" + name
|
||
+ " 已在全局白名单中");
|
||
return true;
|
||
}
|
||
globalPlayerWhitelist.add(name);
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已全局加白: " + name);
|
||
return true;
|
||
}
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别");
|
||
return true;
|
||
}
|
||
String areaName = parsed[0];
|
||
String playerName = parsed[1];
|
||
if (areaName.equalsIgnoreCase("global")) {
|
||
globalPlayerWhitelist.add(playerName);
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已全局加白: "
|
||
+ playerName);
|
||
} else {
|
||
Set<String> wl = areaPlayerWhitelist
|
||
.computeIfAbsent(areaName,
|
||
k -> ConcurrentHashMap
|
||
.newKeySet());
|
||
wl.add(playerName);
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已加白: "
|
||
+ playerName + " → " + areaName);
|
||
}
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect add "
|
||
+ "<区域/玩家> <区域/玩家>");
|
||
return true;
|
||
}
|
||
|
||
// ===== remove 移除白名单 =====
|
||
if (sub.equals("remove")) {
|
||
if (args.length == 2) {
|
||
String name = args[1];
|
||
if (!globalPlayerWhitelist.contains(name)) {
|
||
sender.sendMessage("§e" + name
|
||
+ " 不在全局白名单中");
|
||
return true;
|
||
}
|
||
globalPlayerWhitelist.remove(name);
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已从全局白名单移除: "
|
||
+ name);
|
||
return true;
|
||
}
|
||
if (args.length == 3) {
|
||
String[] parsed = parseAreaAndTarget(
|
||
args[1], args[2]);
|
||
if (parsed == null) {
|
||
sender.sendMessage("§c无法识别");
|
||
return true;
|
||
}
|
||
String areaName = parsed[0];
|
||
String playerName = parsed[1];
|
||
if (areaName.equalsIgnoreCase("global")) {
|
||
globalPlayerWhitelist.remove(playerName);
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已移除: "
|
||
+ playerName);
|
||
} else {
|
||
Set<String> wl =
|
||
areaPlayerWhitelist.get(areaName);
|
||
if (wl != null) {
|
||
wl.remove(playerName);
|
||
}
|
||
saveWhitelists();
|
||
sender.sendMessage("§a已从 " + areaName
|
||
+ " 移除: " + playerName);
|
||
}
|
||
return true;
|
||
}
|
||
sender.sendMessage("§c用法: /protect remove "
|
||
+ "<区域/玩家> <区域/玩家>");
|
||
return true;
|
||
}
|
||
|
||
// ===== on/off/tempon 边框 =====
|
||
if (sub.equals("on")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) {
|
||
p.sendMessage("§c你不在任何防护区域内");
|
||
return true;
|
||
}
|
||
showBorder(p, ac, false);
|
||
p.sendMessage("§a§l[防护] §f已显示 "
|
||
+ ac.name + " 边框");
|
||
return true;
|
||
}
|
||
if (sub.equals("off")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
cancelBorder((Player) sender);
|
||
((Player) sender).sendMessage(
|
||
"§a§l[防护] §f已关闭边框显示");
|
||
return true;
|
||
}
|
||
if (sub.equals("tempon")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p = (Player) sender;
|
||
AreaConfig ac = getArea(
|
||
p.getWorld().getName(),
|
||
p.getLocation().getBlockX(),
|
||
p.getLocation().getBlockY(),
|
||
p.getLocation().getBlockZ());
|
||
if (ac == null) {
|
||
p.sendMessage("§c你不在任何防护区域内");
|
||
return true;
|
||
}
|
||
showBorder(p, ac, true);
|
||
p.sendMessage("§a§l[防护] §f临时显示 "
|
||
+ ac.name + " 边框(15秒)");
|
||
return true;
|
||
}
|
||
|
||
showHelp(sender);
|
||
return true;
|
||
}
|
||
|
||
|
||
private String[] parseAreaAndTarget(String arg1, String arg2) {
|
||
String r1 = resolveAreaName(arg1);
|
||
if (r1 != null) {
|
||
return new String[]{r1, arg2};
|
||
}
|
||
String r2 = resolveAreaName(arg2);
|
||
if (r2 != null) {
|
||
return new String[]{r2, arg1};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private boolean playerExists(String playerName) {
|
||
// 在线玩家
|
||
for (Player online : Bukkit.getOnlinePlayers()) {
|
||
if (online.getName().equalsIgnoreCase(playerName)) return true;
|
||
}
|
||
// 离线玩家
|
||
@SuppressWarnings("deprecation")
|
||
OfflinePlayer op = Bukkit.getOfflinePlayer(playerName);
|
||
return op.hasPlayedBefore();
|
||
}
|
||
|
||
|
||
private boolean validateArea(CommandSender sender, String areaName) {
|
||
if (!areas.containsKey(areaName)) {
|
||
sender.sendMessage("§c区域不存在: " + areaName);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
private String matname(Material mat) {
|
||
String name = mat.name();
|
||
String[] parts = name.split("_");
|
||
StringBuilder sb = new StringBuilder();
|
||
for (String part : parts) {
|
||
if (sb.length() > 0) sb.append(" ");
|
||
sb.append(part.substring(0, 1).toUpperCase())
|
||
.append(part.substring(1).toLowerCase());
|
||
}
|
||
return sb.toString();
|
||
}
|
||
private String yawToDir(float yaw) {
|
||
// MC中: 0=南, 90=西, 180=北, -90/270=东
|
||
yaw = yaw % 360;
|
||
if (yaw < 0) yaw += 360;
|
||
if (yaw >= 315 || yaw < 45) return "南";
|
||
if (yaw >= 45 && yaw < 135) return "西";
|
||
if (yaw >= 135 && yaw < 225) return "北";
|
||
return "东";
|
||
}
|
||
private void saveAreaConfig(AreaConfig ac) {
|
||
File f = new File(rootDir, ac.name + ".txt");
|
||
if (!f.exists()) return;
|
||
try {
|
||
List<String> lines = new ArrayList<>();
|
||
BufferedReader br = new BufferedReader(
|
||
new InputStreamReader(
|
||
new FileInputStream(f),
|
||
StandardCharsets.UTF_8));
|
||
String line;
|
||
while ((line = br.readLine()) != null) {
|
||
if (line.trim().startsWith("起点:")) {
|
||
lines.add("起点: " + ac.world
|
||
+ "," + ac.x1 + "," + ac.z1);
|
||
} else if (line.trim().startsWith("终点:")) {
|
||
lines.add("终点: " + ac.world
|
||
+ "," + ac.x2 + "," + ac.z2);
|
||
} else {
|
||
lines.add(line);
|
||
}
|
||
}
|
||
br.close();
|
||
PrintWriter pw = new PrintWriter(
|
||
new OutputStreamWriter(
|
||
new FileOutputStream(f),
|
||
StandardCharsets.UTF_8));
|
||
for (String l : lines) pw.println(l);
|
||
pw.close();
|
||
} catch (IOException ignored) {}
|
||
}
|
||
|
||
private void showHelp(CommandSender s) {
|
||
s.sendMessage("§e§l==== 区域防护 ====");
|
||
s.sendMessage("§a/protect 工具 §7选地工具");
|
||
s.sendMessage("§a/protect 创建 <名> §7创建区域");
|
||
s.sendMessage("§a/protect 列表 §7区域列表");
|
||
s.sendMessage("§a/protect 重载 §7重载配置");
|
||
s.sendMessage("§a/protect 删除 <名> §7删除区域");
|
||
s.sendMessage("§a/protect add [区域] <玩家> §7玩家加白");
|
||
s.sendMessage("§a/protect remove [区域] <玩家> §7玩家删白");
|
||
s.sendMessage("§a/protect additem [区域] <物品> §7物品加黑");
|
||
s.sendMessage("§a/protect removeitem [区域] <物品> §7物品删黑");
|
||
s.sendMessage("§a/protect on §7显示边框");
|
||
s.sendMessage("§a/protect off §7关闭边框");
|
||
s.sendMessage("§a/protect tempon §7临时边框(15秒)");
|
||
s.sendMessage("§a/protect list §7列出全局白名单");
|
||
s.sendMessage("§a/protect list <区域> §7列出区域白名单");
|
||
s.sendMessage("§a/protect listitem §7列出全局物品黑名单");
|
||
s.sendMessage("§a/protect listitem <区域> §7列出区域物品黑名单");
|
||
|
||
}
|
||
// ==================== 白名单管理公共方法 ====================
|
||
|
||
// ==================== 白名单操作 ====================
|
||
|
||
public boolean isPlayerGlobalWhitelisted(
|
||
String playerName) {
|
||
return globalPlayerWhitelist.contains(playerName);
|
||
}
|
||
|
||
public boolean addPlayerToGlobalWhitelist(
|
||
String playerName) {
|
||
boolean ok = globalPlayerWhitelist.add(playerName);
|
||
if (ok) saveWhitelists();
|
||
return ok;
|
||
}
|
||
|
||
public boolean removePlayerFromGlobalWhitelist(
|
||
String playerName) {
|
||
boolean ok = globalPlayerWhitelist.remove(playerName);
|
||
if (ok) saveWhitelists();
|
||
return ok;
|
||
}
|
||
|
||
public boolean isPlayerAreaWhitelisted(
|
||
String areaName, String playerName) {
|
||
Set<String> wl =
|
||
areaPlayerWhitelist.get(areaName);
|
||
return wl != null && wl.contains(playerName);
|
||
}
|
||
|
||
public boolean addPlayerToAreaWhitelist(
|
||
String areaName, String playerName) {
|
||
Set<String> set = areaPlayerWhitelist
|
||
.computeIfAbsent(areaName,
|
||
k -> ConcurrentHashMap.newKeySet());
|
||
boolean ok = set.add(playerName);
|
||
if (ok) saveWhitelists();
|
||
return ok;
|
||
}
|
||
|
||
public boolean removePlayerFromAreaWhitelist(
|
||
String areaName, String playerName) {
|
||
Set<String> wl =
|
||
areaPlayerWhitelist.get(areaName);
|
||
boolean ok = wl != null && wl.remove(playerName);
|
||
if (ok) saveWhitelists();
|
||
return ok;
|
||
}
|
||
|
||
|
||
|
||
public int handleConfiscate(Player p, AreaConfig ac) {
|
||
if (isPlayerWhitelisted(p.getName(), ac)) return 0;
|
||
Set<String> allItems = new HashSet<>(ac.confiscateItems);
|
||
allItems.addAll(globalItemBlacklist);
|
||
Set<String> areaList = areaItemBlacklist.get(ac.name);
|
||
if (areaList != null) allItems.addAll(areaList);
|
||
|
||
if (allItems.isEmpty()) return 0;
|
||
|
||
int totalRemoved = 0;
|
||
String confiscatedNames = "";
|
||
|
||
for (String itemId : allItems) {
|
||
// 过滤掉区域名和无效ID
|
||
Material mat = Material.matchMaterial(itemId);
|
||
if (mat == null) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 跳过无效物品: " + itemId);
|
||
continue;
|
||
}
|
||
ItemStack[] all = p.getInventory().getContents();
|
||
int count = 0;
|
||
for (int i = 0; i < all.length; i++) {
|
||
if (all[i] != null && all[i].getType() == mat) {
|
||
count += all[i].getAmount();
|
||
all[i] = null;
|
||
}
|
||
}
|
||
p.getInventory().setContents(all);
|
||
|
||
ItemStack off = p.getInventory().getItemInOffHand();
|
||
if (off != null && off.getType() == mat) {
|
||
count += off.getAmount();
|
||
p.getInventory().setItemInOffHand(null);
|
||
}
|
||
|
||
if (count > 0) {
|
||
totalRemoved += count;
|
||
if (!confiscatedNames.isEmpty())
|
||
confiscatedNames += "§7、§e";
|
||
confiscatedNames += "§e" + matname(mat)
|
||
+ " x" + count;
|
||
}
|
||
}
|
||
|
||
if (totalRemoved > 0) {
|
||
String msg = (ac.confiscateMsg != null
|
||
&& !ac.confiscateMsg.isEmpty())
|
||
? ac.confiscateMsg
|
||
: "你携带了违禁品,已被没收";
|
||
p.sendMessage("§c§l[区域防护] §f" + msg);
|
||
p.sendMessage("§7没收物品: " + confiscatedNames);
|
||
|
||
if (ac.enableAnnounce
|
||
&& ac.announceTemplate != null
|
||
&& !ac.announceTemplate.isEmpty()) {
|
||
String ann = ac.announceTemplate
|
||
.replace("{player}", p.getName())
|
||
.replace("{area}", ac.name)
|
||
.replace("{count}", String.valueOf(totalRemoved))
|
||
.replace("{items}", confiscatedNames);
|
||
Bukkit.broadcastMessage("§c§l[区域防护] §f" + ann);
|
||
}
|
||
for (String cmd : ac.punishCommands) {
|
||
String finalCmd = cmd
|
||
.replace("{player}", p.getName())
|
||
.replace("{area}", ac.name)
|
||
.replace("{count}", String.valueOf(totalRemoved));
|
||
Bukkit.getScheduler().runTask(plugin,
|
||
() -> Bukkit.dispatchCommand(
|
||
Bukkit.getConsoleSender(), finalCmd));
|
||
}
|
||
}
|
||
return totalRemoved;
|
||
}
|
||
public void clearBlacklists() {
|
||
globalItemBlacklist.clear();
|
||
areaItemBlacklist.clear();
|
||
plugin.getLogger().info("[防护] 已清空所有黑名单");
|
||
}
|
||
|
||
@EventHandler
|
||
public void onEntityDamage(
|
||
org.bukkit.event.entity.EntityDamageByEntityEvent e) {
|
||
Entity entity = e.getEntity();
|
||
String typeName = entity.getType().name();
|
||
|
||
if (typeName.equals("ITEM_FRAME")
|
||
|| typeName.equals("GLOW_ITEM_FRAME")) {
|
||
if (!(e.getDamager() instanceof Player)) return;
|
||
Player p = (Player) e.getDamager();
|
||
|
||
Set<String> global = globalWhitelist.get("global");
|
||
if (global != null
|
||
&& global.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
AreaConfig ac = findFrameArea(entity);
|
||
if (ac == null || !ac.denyItemFrame) return;
|
||
|
||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||
if (areaWl != null
|
||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||
return;
|
||
}
|
||
|
||
// ===== PVP 逻辑 =====
|
||
if (!(e.getDamager() instanceof Player)) return;
|
||
if (!(e.getEntity() instanceof Player)) return;
|
||
Player p2 = (Player) e.getEntity();
|
||
AreaConfig ac2 = getArea(
|
||
p2.getWorld().getName(),
|
||
p2.getLocation().getBlockX(),
|
||
p2.getLocation().getBlockY(),
|
||
p2.getLocation().getBlockZ());
|
||
if (ac2 != null && ac2.denyPVP
|
||
&& !isPlayerWhitelisted(p2.getName(), ac2)) {
|
||
e.setCancelled(true);
|
||
p2.sendMessage("§c§l[区域防护] §f禁止PVP");
|
||
}
|
||
}
|
||
|
||
// 展示框被点击(旋转/放入/取出)
|
||
@EventHandler
|
||
public void onItemFrameInteract(
|
||
PlayerInteractAtEntityEvent e) {
|
||
Entity clicked = e.getRightClicked();
|
||
String typeName = clicked.getType().name();
|
||
if (!typeName.equals("ITEM_FRAME")
|
||
&& !typeName.equals("GLOW_ITEM_FRAME")) {
|
||
return;
|
||
}
|
||
|
||
Player p = e.getPlayer();
|
||
|
||
// 全局白放行
|
||
Set<String> global = globalWhitelist.get("global");
|
||
if (global != null
|
||
&& global.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
AreaConfig ac = findFrameArea(clicked);
|
||
if (ac == null || !ac.denyItemFrame) return;
|
||
|
||
// 区域白放行
|
||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||
if (areaWl != null
|
||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
// 非白名单 → 拦截
|
||
// 记录当前旋转角度
|
||
final Rotation savedRotation;
|
||
final Entity frame = clicked;
|
||
if (clicked instanceof org.bukkit.entity.ItemFrame) {
|
||
savedRotation =
|
||
((org.bukkit.entity.ItemFrame) clicked)
|
||
.getRotation();
|
||
} else {
|
||
savedRotation = null;
|
||
}
|
||
|
||
e.setCancelled(true);
|
||
|
||
// 下一tick强制恢复旋转
|
||
if (savedRotation != null) {
|
||
final Rotation restore = savedRotation;
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||
if (frame.isValid()
|
||
&& frame instanceof org.bukkit.entity.ItemFrame) {
|
||
((org.bukkit.entity.ItemFrame) frame)
|
||
.setRotation(restore);
|
||
}
|
||
}, 1L);
|
||
}
|
||
|
||
p.sendMessage("§c§l[区域防护] §f禁止交互展示框");
|
||
|
||
// 驱逐远程敌对生物
|
||
banRangedHostiles(p, ac);
|
||
}
|
||
|
||
|
||
|
||
// 展示框保护:检查展示框本身+周围方块所属区域
|
||
|
||
// ===== 展示框攻击拦截(左键/投射物破坏)=====
|
||
@EventHandler
|
||
public void onEntityDamageByEntity(
|
||
org.bukkit.event.entity.EntityDamageByEntityEvent e) {
|
||
Entity entity = e.getEntity();
|
||
String typeName = entity.getType().name();
|
||
|
||
if (typeName.equals("ITEM_FRAME")
|
||
|| typeName.equals("GLOW_ITEM_FRAME")) {
|
||
// 展示框被攻击
|
||
if (!(e.getDamager() instanceof Player)) return;
|
||
Player p = (Player) e.getDamager();
|
||
|
||
// 全局白放行
|
||
Set<String> gwl = globalWhitelist.get("global");
|
||
if (gwl != null
|
||
&& gwl.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
AreaConfig ac = findFrameArea(entity);
|
||
if (ac == null || !ac.denyItemFrame) return;
|
||
|
||
// 区域白放行
|
||
Set<String> awl = areaWhitelist.get(ac.name);
|
||
if (awl != null
|
||
&& awl.contains(p.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||
return;
|
||
}
|
||
|
||
// ===== PVP 逻辑(原有的,不动)=====
|
||
if (!(e.getDamager() instanceof Player)) return;
|
||
if (!(e.getEntity() instanceof Player)) return;
|
||
Player p2 = (Player) e.getEntity();
|
||
AreaConfig ac2 = getArea(
|
||
p2.getWorld().getName(),
|
||
p2.getLocation().getBlockX(),
|
||
p2.getLocation().getBlockY(),
|
||
p2.getLocation().getBlockZ());
|
||
if (ac2 != null && ac2.denyPVP
|
||
&& !isPlayerWhitelisted(p2.getName(), ac2)) {
|
||
e.setCancelled(true);
|
||
p2.sendMessage("§c§l[区域防护] §f禁止PVP");
|
||
}
|
||
}
|
||
private AreaConfig findFrameArea(Entity frame) {
|
||
Location loc = frame.getLocation();
|
||
int bx = loc.getBlockX();
|
||
int by = loc.getBlockY();
|
||
int bz = loc.getBlockZ();
|
||
String world = loc.getWorld().getName();
|
||
|
||
AreaConfig ac = getArea(world, bx, by, bz);
|
||
if (ac != null && ac.denyItemFrame) return ac;
|
||
|
||
int[][] offsets = {
|
||
{0, 0, 1}, {0, 0, -1},
|
||
{1, 0, 0}, {-1, 0, 0},
|
||
{0, 1, 0}, {0, -1, 0}
|
||
};
|
||
for (int[] off : offsets) {
|
||
ac = getArea(world,
|
||
bx + off[0], by + off[1], bz + off[2]);
|
||
if (ac != null && ac.denyItemFrame) return ac;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ===== 投射物击中展示框(弓箭/弩)=====
|
||
@EventHandler
|
||
public void onProjectileHit(
|
||
org.bukkit.event.entity.ProjectileHitEvent e) {
|
||
if (!(e.getEntity().getShooter() instanceof Player)) return;
|
||
Player shooter = (Player) e.getEntity().getShooter();
|
||
Entity hit = e.getHitEntity();
|
||
if (hit == null) return;
|
||
String typeName = hit.getType().name();
|
||
if (!typeName.equals("ITEM_FRAME")
|
||
&& !typeName.equals("GLOW_ITEM_FRAME")) {
|
||
return;
|
||
}
|
||
|
||
// 全局白放行
|
||
Set<String> gwl = globalWhitelist.get("global");
|
||
if (gwl != null
|
||
&& gwl.contains(shooter.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
AreaConfig ac = findFrameArea(hit);
|
||
if (ac == null || !ac.denyItemFrame) return;
|
||
|
||
// 区域白放行
|
||
Set<String> awl = areaWhitelist.get(ac.name);
|
||
if (awl != null
|
||
&& awl.contains(shooter.getName().toLowerCase())) {
|
||
return;
|
||
}
|
||
|
||
e.setCancelled(true);
|
||
shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||
}
|
||
|
||
|
||
// ===== 区域外火焰不能蔓延进区域 =====
|
||
@EventHandler
|
||
public void onBlockSpread(BlockSpreadEvent e) {
|
||
Block block = e.getBlock();
|
||
AreaConfig tgtArea = getArea(
|
||
block.getWorld().getName(),
|
||
block.getX(), block.getY(), block.getZ());
|
||
if (tgtArea != null && tgtArea.denyFireSpread) {
|
||
e.setCancelled(true);
|
||
}
|
||
}
|
||
|
||
// ===== 区域内禁止点燃方块 =====
|
||
@EventHandler
|
||
public void onIgnite(BlockIgniteEvent e) {
|
||
Player p = e.getPlayer();
|
||
if (p == null) return; // 非玩家点燃的(闪电等)
|
||
Block block = e.getBlock();
|
||
AreaConfig ac = getArea(
|
||
block.getWorld().getName(),
|
||
block.getX(), block.getY(), block.getZ());
|
||
if (ac != null && ac.denyFireSpread) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c§l[区域防护] §f此区域禁止点燃物品");
|
||
}
|
||
}
|
||
|
||
// ===== 区域内方块着火(TNT、岩浆等)也阻止 =====
|
||
@EventHandler
|
||
public void onBlockBurn(BlockBurnEvent e) {
|
||
Block block = e.getBlock();
|
||
AreaConfig ac = getArea(
|
||
block.getWorld().getName(),
|
||
block.getX(), block.getY(), block.getZ());
|
||
if (ac != null && ac.denyFireSpread)
|
||
e.setCancelled(true);
|
||
}
|
||
|
||
|
||
private void savePeaceWhitelist(AreaConfig ac) {
|
||
File file = new File(rootDir, ac.name + ".txt");
|
||
if (!file.exists()) return;
|
||
|
||
try {
|
||
List<String> lines = new ArrayList<>();
|
||
BufferedReader reader = new BufferedReader(
|
||
new FileReader(file, StandardCharsets.UTF_8));
|
||
String line;
|
||
boolean found = false;
|
||
|
||
while ((line = reader.readLine()) != null) {
|
||
String trimmed = line.trim();
|
||
if (trimmed.contains("和平")
|
||
&& trimmed.contains("白名单")) {
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append("和平模式白名单:");
|
||
boolean first = true;
|
||
for (String name : ac.peaceWhitelist) {
|
||
if (!first) sb.append(",");
|
||
sb.append(name);
|
||
first = false;
|
||
}
|
||
lines.add(sb.toString());
|
||
found = true;
|
||
} else {
|
||
lines.add(line);
|
||
}
|
||
}
|
||
reader.close();
|
||
|
||
if (!found && !ac.peaceWhitelist.isEmpty()) {
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append("和平模式白名单:");
|
||
boolean first = true;
|
||
for (String name : ac.peaceWhitelist) {
|
||
if (!first) sb.append(",");
|
||
sb.append(name);
|
||
first = false;
|
||
}
|
||
lines.add(sb.toString());
|
||
}
|
||
|
||
FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8);
|
||
for (String l : lines) {
|
||
writer.write(l + "\n");
|
||
}
|
||
writer.close();
|
||
} catch (IOException e) {
|
||
plugin.getLogger().warning(
|
||
"[防护] 保存和平白名单失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onPaintingPlace(
|
||
org.bukkit.event.player.PlayerBucketEmptyEvent e) {
|
||
// 暂不处理
|
||
}
|
||
|
||
|
||
// ==================== 数据类 ====================
|
||
|
||
public static class AreaConfig {
|
||
public String name = "";
|
||
public String world = "";
|
||
public int x1, z1, x2, z2;
|
||
public int yMin = 0, yMax = 255;
|
||
public List<String> confiscateItems = new ArrayList<>();
|
||
public List<String> denyUseItems = new ArrayList<>();
|
||
public List<String> clearEffects = new ArrayList<>(); // 清除单个效果名
|
||
public boolean clearAllBadEffects = false; // 清除所有负面+中性
|
||
public List<String[]> giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数]
|
||
public List<String> punishCommands = new ArrayList<>();
|
||
public boolean denyBlockPlace = false;
|
||
public boolean denyBlockBreak = false;
|
||
public boolean denyPVP = false;
|
||
public boolean denyFallDamage = false;
|
||
public boolean denyHunger = false;
|
||
public boolean denyAllDamage = false;
|
||
public boolean denyMount = false;
|
||
public boolean denyDrop = false;
|
||
public boolean denyEnderPearl = false;
|
||
public boolean denyBow = false;
|
||
public boolean denyPotion = false;
|
||
public boolean denyExplosion = false;
|
||
public boolean enableAnnounce = false;
|
||
public String announceTemplate = "";
|
||
public String confiscateMsg = "";
|
||
public String enterMsg = "";
|
||
public String leaveMsg = "";
|
||
public boolean denyRaid = false;
|
||
public boolean denyFireSpread = false;
|
||
public boolean denyAllEffects = false;;
|
||
public boolean peaceMode = false;
|
||
public boolean denyItemFrame = false;
|
||
public Set<String> peaceWhitelist = new HashSet<>();
|
||
public String enforceGameMode = null; // 强制游戏模式
|
||
public Set<String> modeExempt = new HashSet<>(); // 模式排除名单
|
||
|
||
|
||
|
||
|
||
private static final Map<String, PotionEffectType> EFFECT_MAP
|
||
= new HashMap<>();
|
||
static {
|
||
// 负面效果
|
||
EFFECT_MAP.put("缓慢", PotionEffectType.SLOWNESS);
|
||
EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE);
|
||
EFFECT_MAP.put("瞬间伤害", PotionEffectType.INSTANT_DAMAGE);
|
||
EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA);
|
||
EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS);
|
||
EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER);
|
||
EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS);
|
||
EFFECT_MAP.put("中毒", PotionEffectType.POISON);
|
||
EFFECT_MAP.put("凋零", PotionEffectType.WITHER);
|
||
EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION);
|
||
EFFECT_MAP.put("霉运", PotionEffectType.UNLUCK);
|
||
EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS);
|
||
EFFECT_MAP.put("蓄风", PotionEffectType.WIND_CHARGED);
|
||
EFFECT_MAP.put("盘丝", PotionEffectType.WEAVING);
|
||
EFFECT_MAP.put("渗浆", PotionEffectType.OOZING);
|
||
EFFECT_MAP.put("寄生", PotionEffectType.INFESTED);
|
||
// 中性效果
|
||
EFFECT_MAP.put("不祥之兆", PotionEffectType.BAD_OMEN);
|
||
EFFECT_MAP.put("不祥征兆", PotionEffectType.BAD_OMEN);
|
||
EFFECT_MAP.put("袭击之兆", PotionEffectType.RAID_OMEN);
|
||
EFFECT_MAP.put("袭击征兆", PotionEffectType.RAID_OMEN);
|
||
EFFECT_MAP.put("试炼之兆", PotionEffectType.TRIAL_OMEN);
|
||
EFFECT_MAP.put("试炼征兆", PotionEffectType.TRIAL_OMEN);
|
||
// 正面效果
|
||
EFFECT_MAP.put("迅捷", PotionEffectType.SPEED);
|
||
EFFECT_MAP.put("急迫", PotionEffectType.HASTE);
|
||
EFFECT_MAP.put("力量", PotionEffectType.STRENGTH);
|
||
EFFECT_MAP.put("瞬间治疗", PotionEffectType.INSTANT_HEALTH);
|
||
EFFECT_MAP.put("跳跃提升", PotionEffectType.JUMP_BOOST);
|
||
EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION);
|
||
EFFECT_MAP.put("抗性提升", PotionEffectType.RESISTANCE);
|
||
EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE);
|
||
EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING);
|
||
EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY);
|
||
EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION);
|
||
EFFECT_MAP.put("发光", PotionEffectType.GLOWING);
|
||
EFFECT_MAP.put("生命提升", PotionEffectType.HEALTH_BOOST);
|
||
EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION);
|
||
EFFECT_MAP.put("饱和", PotionEffectType.SATURATION);
|
||
EFFECT_MAP.put("幸运", PotionEffectType.LUCK);
|
||
EFFECT_MAP.put("村庄英雄", PotionEffectType.HERO_OF_THE_VILLAGE);
|
||
EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING);
|
||
EFFECT_MAP.put("潮涌能量", PotionEffectType.CONDUIT_POWER);
|
||
EFFECT_MAP.put("海豚的恩惠", PotionEffectType.DOLPHINS_GRACE);
|
||
|
||
|
||
// 带效果二字
|
||
// 补充带"效果"后缀的别名
|
||
EFFECT_MAP.put("黑暗效果", PotionEffectType.DARKNESS);
|
||
EFFECT_MAP.put("不祥之兆效果", PotionEffectType.BAD_OMEN);
|
||
EFFECT_MAP.put("袭击之兆效果", PotionEffectType.RAID_OMEN);
|
||
EFFECT_MAP.put("试炼之兆效果", PotionEffectType.TRIAL_OMEN);
|
||
EFFECT_MAP.put("缓慢效果", PotionEffectType.SLOWNESS);
|
||
EFFECT_MAP.put("中毒效果", PotionEffectType.POISON);
|
||
EFFECT_MAP.put("凋零效果", PotionEffectType.WITHER);
|
||
EFFECT_MAP.put("失明效果", PotionEffectType.BLINDNESS);
|
||
EFFECT_MAP.put("饥饿效果", PotionEffectType.HUNGER);
|
||
EFFECT_MAP.put("虚弱效果", PotionEffectType.WEAKNESS);
|
||
EFFECT_MAP.put("反胃效果", PotionEffectType.NAUSEA);
|
||
EFFECT_MAP.put("飘浮效果", PotionEffectType.LEVITATION);
|
||
EFFECT_MAP.put("发光效果", PotionEffectType.GLOWING);
|
||
EFFECT_MAP.put("霉运效果", PotionEffectType.UNLUCK);
|
||
EFFECT_MAP.put("蓄风效果", PotionEffectType.WIND_CHARGED);
|
||
EFFECT_MAP.put("盘丝效果", PotionEffectType.WEAVING);
|
||
EFFECT_MAP.put("渗浆效果", PotionEffectType.OOZING);
|
||
EFFECT_MAP.put("寄生效果", PotionEffectType.INFESTED);
|
||
EFFECT_MAP.put("挖掘疲劳效果", PotionEffectType.MINING_FATIGUE);
|
||
EFFECT_MAP.put("瞬间伤害效果", PotionEffectType.INSTANT_DAMAGE);
|
||
EFFECT_MAP.put("村庄英雄效果", PotionEffectType.HERO_OF_THE_VILLAGE);
|
||
}
|
||
|
||
|
||
public int ruleCount() {
|
||
int c = 0;
|
||
if (!confiscateItems.isEmpty()) c++;
|
||
if (denyBlockPlace) c++;
|
||
if (denyBlockBreak) c++;
|
||
if (denyPVP) c++;
|
||
if (denyFallDamage) c++;
|
||
if (denyHunger) c++;
|
||
if (denyAllDamage) c++;
|
||
if (denyDrop) c++;
|
||
if (denyMount) c++;
|
||
if (denyEnderPearl) c++;
|
||
if (denyBow) c++;
|
||
if (denyRaid) c++;
|
||
if (denyPotion) c++;
|
||
if (denyExplosion) c++;
|
||
if (denyFireSpread) c++;
|
||
if (denyAllEffects) c++;
|
||
if (!clearEffects.isEmpty()) c++;
|
||
if (clearAllBadEffects) c++;
|
||
if (!giveEffects.isEmpty()) c++;
|
||
if (enableAnnounce) c++;
|
||
if (!punishCommands.isEmpty()) c++;
|
||
if (!denyUseItems.isEmpty()) c++;
|
||
if (peaceMode) c++;
|
||
if (denyItemFrame) c++;
|
||
if (!peaceWhitelist.isEmpty()) c++;
|
||
|
||
return c;
|
||
}
|
||
}
|
||
} |