负面效果从增益里拆出独立子菜单 + 药水成就防作弊
用户要求:增益只归纳增益,负面另开子菜单;移除指定效果那套不动。 插件给的效果必须打标记,靠插件凑齐原版药水成就的立刻撤销。 效果拆分 - AreaConfig 新增 badEffects 字段,area_lands 加 bad_effects 列(73 列) - GUI:效果管理主菜单 11~15 连排五格(清负面/禁效果/单清/增益/负面), 新增 subPage 4 负面效果列表;选择页按 clear/give/bad 分别只列对应档位 - CLI:新增 effectsmgmt 6/7 子页与 effectsbadadd/effectsbadremove/effectsbadedit 指令、editBad_ 回写分支,与 GUI 同一份效果口径 - 施加点全部合流 giveRegionEffects/applyEffectList,并修正 handleEnter 的先清后给顺序(否则负面配置会被 clearBadEffects 清掉) - 数量上限改为增益+负面合计(用户组 max_effects 是同一份预算) 成就防作弊 - 新增 AchievementEffectGuard:接管 nether/all_potions(17种) 与 nether/all_effects(34种),清单抄自服务端 advancement json - EntityPotionEffectEvent(Cause.PLUGIN) + PlayerAdvancementCriterionGrantEvent 双钩子,revokeCriteria 撤销,进服复核一次,离场清标记
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityPotionEffectEvent;
|
||||
import com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* ★ 药水类成就防作弊(2026-10-04 用户要求)
|
||||
*
|
||||
* <p><b>需求原文</b>:「插件给的一定要做标记,原版成就判断出来就查,如果有人靠插件药水到手
|
||||
* 获得,立即撤销。……两条关联成就必须要玩家手打。绝不能靠插件作弊。」</p>
|
||||
*
|
||||
* <p><b>为什么是这两条</b>:Java 版只有两条成就是「同时身上带着某一整套效果」即可完成,
|
||||
* 触发器都是 {@code minecraft:effects_changed}(每次效果增删都触发,不看来源):</p>
|
||||
* <ul>
|
||||
* <li>{@code minecraft:nether/all_potions} —— A Furious Cocktail,需 17 种效果同时存在</li>
|
||||
* <li>{@code minecraft:nether/all_effects} —— How Did We Get Here?,需 34 种效果同时存在</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>为什么要撤销而不是拦 toast</b>:Toast 推送由客户端与服务端共同完成,
|
||||
* 单靠服务端无法保证「不弹」,但可以在授予的同一 tick 内撤销进度——
|
||||
* 玩家在成绩界面看到的就是「未完成」,这就满足了需求里的「服务端撤销即可」。</p>
|
||||
*
|
||||
* <p><b>判定口径</b>:只要玩家当前身上的效果集合里,<b>有任意一项</b>是本插件给出去的
|
||||
* (即带插件标记),就撤销这两条成就。<b>不撤销别的成就</b>——只有这两条的判定完全由
|
||||
* 效果集合构成,其他成就(酿造药水、击杀生物、探险家等)跟插件效果无关,误伤代价大。</p>
|
||||
*
|
||||
* <p><b>标记的建立</b>:插件每一次 {@code addPotionEffect} 成功后,都会把效果类型登记进
|
||||
* {@link #pluginMarked}(见 {@code AreaProtection.markPluginEffects})。这份标记在玩家退出
|
||||
* 领地、效果自然到期时统一清理,避免「插件给过一次就永久背锅」。</p>
|
||||
*/
|
||||
public class AchievementEffectGuard implements Listener {
|
||||
|
||||
// ==================== 成就效果清单(服务端 advancement json 原文) ====================
|
||||
|
||||
/**
|
||||
* A Furious Cocktail({@code nether/all_potions})要求的 17 种效果。
|
||||
* 取自 {@code data/minecraft/advancement/nether/all_potions.json} 的 criteria.effects 键集。
|
||||
*/
|
||||
private static final Set<String> ADV_SET_ALL_POTIONS = Set.of(
|
||||
"fire_resistance", "infested", "invisibility", "jump_boost", "night_vision",
|
||||
"oozing", "poison", "regeneration", "resistance", "slow_falling", "slowness",
|
||||
"speed", "strength", "water_breathing", "weakness", "weaving", "wind_charged");
|
||||
|
||||
/**
|
||||
* How Did We Get Here?({@code nether/all_effects})要求的 34 种效果。
|
||||
* 注意这份清单里<b>混有负面效果</b>(中毒/缓慢/虚弱/凋零/反胃…),
|
||||
* 所以 2026-10-04 把负面配置拆成独立子菜单之后,防作弊覆盖面反而更完整。
|
||||
*/
|
||||
private static final Set<String> ADV_SET_ALL_EFFECTS = Set.of(
|
||||
"absorption", "bad_omen", "blindness", "breath_of_the_nautilus", "conduit_power",
|
||||
"darkness", "dolphins_grace", "fire_resistance", "glowing", "haste",
|
||||
"hero_of_the_village", "hunger", "infested", "invisibility", "jump_boost",
|
||||
"levitation", "mining_fatigue", "nausea", "night_vision", "oozing", "poison",
|
||||
"raid_omen", "regeneration", "resistance", "slow_falling", "slowness", "speed",
|
||||
"strength", "trial_omen", "water_breathing", "weakness", "weaving", "wind_charged",
|
||||
"wither");
|
||||
|
||||
/** 两条成就的 key(延迟解析,插件早期启动时 advancement 尚未加载完) */
|
||||
private static final String ADV_KEY_ALL_POTIONS = "minecraft:nether/all_potions";
|
||||
private static final String ADV_KEY_ALL_EFFECTS = "nether/all_effects";
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
/** 插件给出去的效果标记:玩家 UUID → 本插件施加过的效果类型名(小写英文 ID) */
|
||||
private final Map<UUID, Set<String>> pluginMarked = new HashMap<>();
|
||||
|
||||
/** 已完成撤销的玩家,避免每次事件都重复刷日志 */
|
||||
private final Set<UUID> revokedLogged = new HashSet<>();
|
||||
|
||||
/** 每个玩家的判定冷却:同一玩家 1 秒内最多判一次,防抖 */
|
||||
private final Map<UUID, Long> lastCheckTs = new HashMap<>();
|
||||
|
||||
private static final long CHECK_COOLDOWN_MS = 1000L;
|
||||
|
||||
public AchievementEffectGuard(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
// ==================== 标记接口(供 AreaProtection 调用) ====================
|
||||
|
||||
/**
|
||||
* 登记「这些效果是插件给的」。
|
||||
*
|
||||
* @param types 本插件刚刚施加成功的效果类型
|
||||
*/
|
||||
public void markPluginEffects(UUID uid, List<PotionEffectType> types) {
|
||||
if (uid == null || types == null || types.isEmpty()) return;
|
||||
Set<String> set = pluginMarked.computeIfAbsent(uid, k -> new HashSet<>());
|
||||
for (PotionEffectType t : types) {
|
||||
if (t == null) continue;
|
||||
set.add(normalizeEffectId(t));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清掉某玩家的插件效果标记(离开领地 / 插件不再持有该效果时调用)。
|
||||
*
|
||||
* @param keepStillApplied 仍由插件持有的效果,保留标记;其余标记删除
|
||||
*/
|
||||
public void clearPluginEffects(UUID uid, Set<PotionEffectType> keepStillApplied) {
|
||||
if (uid == null) return;
|
||||
Set<String> keep = new HashSet<>();
|
||||
if (keepStillApplied != null) {
|
||||
for (PotionEffectType t : keepStillApplied) {
|
||||
if (t != null) keep.add(normalizeEffectId(t));
|
||||
}
|
||||
}
|
||||
Set<String> cur = pluginMarked.get(uid);
|
||||
if (cur == null) return;
|
||||
cur.retainAll(keep);
|
||||
if (cur.isEmpty()) pluginMarked.remove(uid);
|
||||
}
|
||||
|
||||
/** 玩家退出时彻底清账 */
|
||||
public void forgetPlayer(UUID uid) {
|
||||
if (uid == null) return;
|
||||
pluginMarked.remove(uid);
|
||||
revokedLogged.remove(uid);
|
||||
lastCheckTs.remove(uid);
|
||||
}
|
||||
|
||||
/** 该玩家当前是否还带着插件给的效果(诊断用) */
|
||||
public boolean hasPluginMarkedEffects(Player p) {
|
||||
if (p == null) return false;
|
||||
Set<String> marked = pluginMarked.get(p.getUniqueId());
|
||||
if (marked == null || marked.isEmpty()) return false;
|
||||
for (PotionEffect eff : p.getActivePotionEffects()) {
|
||||
if (marked.contains(normalizeEffectId(eff.getType()))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== 事件监听 ====================
|
||||
|
||||
/**
|
||||
* 每次效果变化都复核一遍。
|
||||
*
|
||||
* <p>用 LOWEST 优先级 + {@code ignoreCancelled = false}:即便别的插件把事件取消了
|
||||
* (等于说效果最终会被加上),我们也已经先记录并撤销了。</p>
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
|
||||
public void onPotionEffectChange(EntityPotionEffectEvent event) {
|
||||
if (!(event.getEntity() instanceof Player)) return;
|
||||
Player p = (Player) event.getEntity();
|
||||
|
||||
// 插件自己施加的:立刻打标记(与 AreaProtection 的登记互为兜底)
|
||||
if (event.getCause() == EntityPotionEffectEvent.Cause.PLUGIN
|
||||
&& event.getAction() != EntityPotionEffectEvent.Action.CLEARED
|
||||
&& event.getAction() != EntityPotionEffectEvent.Action.REMOVED) {
|
||||
PotionEffectType type = event.getModifiedType();
|
||||
if (type != null) {
|
||||
markPluginEffects(p.getUniqueId(), Collections.singletonList(type));
|
||||
}
|
||||
}
|
||||
|
||||
// 效果被移除/清除时,同步把标记里已经不生效的项剔掉
|
||||
if (event.getAction() == EntityPotionEffectEvent.Action.CLEARED) {
|
||||
pluginMarked.remove(p.getUniqueId());
|
||||
} else if (event.getAction() == EntityPotionEffectEvent.Action.REMOVED) {
|
||||
Set<String> marked = pluginMarked.get(p.getUniqueId());
|
||||
if (marked != null && event.getModifiedType() != null) {
|
||||
marked.remove(normalizeEffectId(event.getModifiedType()));
|
||||
if (marked.isEmpty()) pluginMarked.remove(p.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
scheduleCheck(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成就授予的那一刻直接撤销(兜底,覆盖 effects_changed 之外的授予路径)。
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
|
||||
public void onAdvancementCriterionGrant(PlayerAdvancementCriterionGrantEvent event) {
|
||||
Advancement adv = event.getAdvancement();
|
||||
if (adv == null || adv.getKey() == null) return;
|
||||
String key = adv.getKey().toString(); // 形如 nether/all_potions
|
||||
if (!isGuardedAdvancement(key)) return;
|
||||
|
||||
// 只有当玩家身上确实带着插件给的效果时才撤,避免误伤纯手打玩家
|
||||
if (!hasPluginMarkedEffects(event.getPlayer())) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
revokeAll(event.getPlayer(), key, "成就授予时检测到插件来源效果");
|
||||
}
|
||||
|
||||
/** 玩家进服立刻复核一次(防止插件重启期间拿到成就、或上次下线前漏撤) */
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
// 延后一 tick:刚进服时 effect / advancement 数据可能还没完全同步
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
Player p = event.getPlayer();
|
||||
if (p != null && p.isOnline()) {
|
||||
checkAndRevoke(p, false);
|
||||
}
|
||||
}, 20L);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
forgetPlayer(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
// ==================== 核心判定 ====================
|
||||
|
||||
/**
|
||||
* 排一个下一 tick 的复核任务(同一 tick 内插件可能批量加多个效果,
|
||||
* 每次都全量扫描 34 项是浪费)。
|
||||
*/
|
||||
private void scheduleCheck(Player p) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (p != null && p.isOnline()) {
|
||||
checkAndRevoke(p, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定并撤销。
|
||||
*
|
||||
* @param useCooldown 是否走 1 秒冷却(进服复核不走冷却)
|
||||
*/
|
||||
private void checkAndRevoke(Player p, boolean useCooldown) {
|
||||
UUID uid = p.getUniqueId();
|
||||
long now = System.currentTimeMillis();
|
||||
if (useCooldown) {
|
||||
Long last = lastCheckTs.get(uid);
|
||||
if (last != null && now - last < CHECK_COOLDOWN_MS) return;
|
||||
lastCheckTs.put(uid, now);
|
||||
}
|
||||
|
||||
Set<String> marked = pluginMarked.get(uid);
|
||||
if (marked == null || marked.isEmpty()) return; // 没被插件碰过 → 绝不碰成就
|
||||
|
||||
// 玩家当前实际生效的效果集合
|
||||
Set<String> active = new HashSet<>();
|
||||
for (PotionEffect eff : p.getActivePotionEffects()) {
|
||||
active.add(normalizeEffectId(eff.getType()));
|
||||
}
|
||||
|
||||
// 已被自然到期/喝牛奶清掉的标记不参与判定
|
||||
Set<String> live = new HashSet<>();
|
||||
for (String m : marked) {
|
||||
if (active.contains(m)) live.add(m);
|
||||
}
|
||||
if (live.isEmpty()) {
|
||||
pluginMarked.remove(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 关键判定:两条成就要求的清单里,只要有任意一项在 live 里 → 撤销
|
||||
List<String> hitAllPotions = new ArrayList<>();
|
||||
for (String need : ADV_SET_ALL_POTIONS) {
|
||||
if (live.contains(need)) hitAllPotions.add(need);
|
||||
}
|
||||
List<String> hitAllEffects = new ArrayList<>();
|
||||
for (String need : ADV_SET_ALL_EFFECTS) {
|
||||
if (live.contains(need)) hitAllEffects.add(need);
|
||||
}
|
||||
|
||||
if (!hitAllPotions.isEmpty()) {
|
||||
revokeAll(p, ADV_KEY_ALL_POTIONS, "插件来源效果 " + summarize(hitAllPotions));
|
||||
}
|
||||
if (!hitAllEffects.isEmpty()) {
|
||||
revokeAll(p, ADV_KEY_ALL_EFFECTS, "插件来源效果 " + summarize(hitAllEffects));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销一条受管控成就的全部条件。
|
||||
*
|
||||
* <p>用 {@code revokeCriteria} 逐条撤,而不是只撤当前触发的那个 criterion——
|
||||
* 两条成就都只有 {@code all_effects} 一个 criterion,撤它即可让整条回到未完成。</p>
|
||||
*/
|
||||
private void revokeAll(Player p, String advKey, String reason) {
|
||||
Advancement adv = resolveAdvancement(advKey);
|
||||
if (adv == null) return;
|
||||
|
||||
AdvancementProgress progress = p.getAdvancementProgress(adv);
|
||||
if (progress == null || !progress.isDone()) return; // 本来就没完成,不动
|
||||
|
||||
Set<String> remaining = new HashSet<>(progress.getRemainingCriteria());
|
||||
int revoked = 0;
|
||||
for (String crit : progress.getAwardedCriteria()) {
|
||||
if (progress.revokeCriteria(crit)) revoked++;
|
||||
}
|
||||
// 少数情况 revokeCriteria 返回 false(条件本来就不成立),补一次兜底
|
||||
if (revoked == 0 && !remaining.isEmpty()) {
|
||||
for (String crit : progress.getRemainingCriteria()) {
|
||||
remaining.remove(crit);
|
||||
}
|
||||
}
|
||||
|
||||
if (revokedLogged.add(p.getUniqueId())) {
|
||||
plugin.getLogger().warning("[成就防作弊] 已撤销 " + advKey
|
||||
+ " 玩家=" + p.getName()
|
||||
+ " 原因=" + reason
|
||||
+ " 撤销条件数=" + revoked
|
||||
+ "(插件提供方便,不提供作弊;这两条成就必须手打)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 advancement:优先用 NamespacedKey({@code Bukkit.getAdvancement}),
|
||||
* 失败则回退成 Bukkit 可识别的短 key。
|
||||
*/
|
||||
private Advancement resolveAdvancement(String key) {
|
||||
String plain = key.startsWith("minecraft:") ? key.substring("minecraft:".length()) : key;
|
||||
Advancement adv = null;
|
||||
try {
|
||||
adv = Bukkit.getAdvancement(NamespacedKey.minecraft(plain));
|
||||
} catch (Throwable ignored) { }
|
||||
if (adv == null) {
|
||||
try {
|
||||
adv = Bukkit.getAdvancement(new NamespacedKey("minecraft", plain));
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
return adv;
|
||||
}
|
||||
|
||||
/** 是否是本插件要管的成就(只管这两条 potion 类成就) */
|
||||
private boolean isGuardedAdvancement(String key) {
|
||||
if (key == null) return false;
|
||||
String plain = key.startsWith("minecraft:") ? key.substring("minecraft:".length()) : key;
|
||||
return plain.equals(ADV_KEY_ALL_POTIONS) || plain.equals(ADV_KEY_ALL_EFFECTS);
|
||||
}
|
||||
|
||||
// ==================== 工具 ====================
|
||||
|
||||
/**
|
||||
* 效果类型统一成小写英文 ID(如 {@code SLOWNESS} → {@code slowness})。
|
||||
*
|
||||
* <p>不同 Paper 版本的 {@code PotionEffectType#getKey()} 可能带命名空间,
|
||||
* 这里只取 {@code :} 后半段,和 advancement json 的写法对齐。</p>
|
||||
*/
|
||||
private static String normalizeEffectId(PotionEffectType type) {
|
||||
if (type == null) return "";
|
||||
String key;
|
||||
try {
|
||||
key = type.getKey().toString(); // 形如 minecraft:speed
|
||||
} catch (Throwable t) {
|
||||
key = type.getName(); // 老版本退路
|
||||
}
|
||||
if (key == null) return "";
|
||||
int idx = key.indexOf(':');
|
||||
if (idx >= 0) key = key.substring(idx + 1);
|
||||
return key.toLowerCase(java.util.Locale.ROOT);
|
||||
}
|
||||
|
||||
/** 命中清单的可读化截断,避免日志刷屏 */
|
||||
private static String summarize(List<String> hit) {
|
||||
if (hit.size() <= 6) return String.join(",", hit);
|
||||
return String.join(",", hit.subList(0, 6)) + " …共" + hit.size() + "项";
|
||||
}
|
||||
}
|
||||
@@ -1304,6 +1304,8 @@ public class AreaCLIManager {
|
||||
if (subPage == 1) {
|
||||
p.sendMessage(header("效果管理: " + landName));
|
||||
p.sendMessage(Component.text("§7清除所有负面效果: §f" + (land.clearAllBadEffects ? "§a开启" : "§c关闭")));
|
||||
p.sendMessage(Component.text("§7当前增益: §a" + land.giveEffects.size()
|
||||
+ " §f个 §7当前负面: §c" + land.badEffects.size() + " §f个"));
|
||||
p.sendMessage(Component.text("§7禁止所有效果: §f" + (land.denyAllEffects ? "§a开启" : "§c关闭")));
|
||||
|
||||
// 全清负面
|
||||
@@ -1331,6 +1333,8 @@ public class AreaCLIManager {
|
||||
p.sendMessage(clickableAction("◀", "返回领地管理", "/protect cli landmanage " + landName + " 1"));
|
||||
p.sendMessage(clickableAction("➡", "单清效果", "/protect cli effectsmgmt " + landName + " 2"));
|
||||
p.sendMessage(clickableAction("➡", "添加增益效果", "/protect cli effectsmgmt " + landName + " 3"));
|
||||
// ★ 2026-10-04 用户要求:增益/负面拆开,负面独立入口
|
||||
p.sendMessage(clickableAction("➡", "添加负面效果", "/protect cli effectsmgmt " + landName + " 6"));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
@@ -1511,6 +1515,105 @@ public class AreaCLIManager {
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsmgmt " + landName + " 3")));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
// ========== 子菜单6:添加负面效果(2026-10-04 从增益拆出) ==========
|
||||
else if (subPage == 6) {
|
||||
p.sendMessage(header("添加负面效果: " + landName));
|
||||
p.sendMessage(Component.text("§7当前负面效果列表:"));
|
||||
|
||||
if (land.badEffects.isEmpty()) {
|
||||
p.sendMessage(Component.text("§7(空)"));
|
||||
} else {
|
||||
for (int i = 0; i < land.badEffects.size(); i++) {
|
||||
String[] eff = land.badEffects.get(i);
|
||||
String desc = eff[0] + (eff.length > 1 ? " Lv" + eff[1] : "") + (eff.length > 2 ? " §f" + eff[2] + "秒" : "");
|
||||
String level = eff.length > 1 ? eff[1] : "1";
|
||||
String duration = eff.length > 2 ? eff[2] : "300";
|
||||
p.sendMessage(Component.empty()
|
||||
.append(Component.text("§c- " + desc + " "))
|
||||
.append(Component.text("§b[等级:" + level + "]")
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e点击编辑等级")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsbadedit " + landName + " " + (i + 1) + " level")))
|
||||
.append(Component.text(" "))
|
||||
.append(Component.text("§d[时长:" + duration + "s]")
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e点击编辑时长(秒)")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsbadedit " + landName + " " + (i + 1) + " duration")))
|
||||
.append(Component.text(" "))
|
||||
.append(Component.text("§c[x]")
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§c点击移除负面效果 '" + desc + "'")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsbadremove " + landName + " " + (i + 1)))));
|
||||
}
|
||||
}
|
||||
|
||||
p.sendMessage(Component.text(""));
|
||||
p.sendMessage(Component.text("")
|
||||
.append(Component.text("§c[添加负面]"))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("§e点击选择要添加的负面效果")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsmgmt " + landName + " 7")));
|
||||
p.sendMessage(Component.text("§a[◀ 返回管理菜单]")
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsmgmt " + landName + " 1")));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
// ========== 子菜单7:选择负面效果(负面 + 中性,不含正面) ==========
|
||||
else if (subPage == 7) {
|
||||
p.sendMessage(header("选择要添加的负面效果: " + landName));
|
||||
p.sendMessage(Component.text("§7点击效果名称直接添加(默认等级1,持续300秒)"));
|
||||
p.sendMessage(Component.text("§7高级: /protect cli effectsbadadd <效果名> [等级] [秒数]"));
|
||||
|
||||
// ★ 与 GUI 的 EFFECTS_BAD / EFFECTS_NEUTRAL 保持同一份口径
|
||||
String[][] badList = {
|
||||
{"缓慢", "slowness"}, {"挖掘疲劳", "mining_fatigue"}, {"瞬间伤害", "instant_damage"},
|
||||
{"反胃", "nausea"}, {"失明", "blindness"}, {"饥饿", "hunger"},
|
||||
{"虚弱", "weakness"}, {"中毒", "poison"}, {"凋零", "wither"},
|
||||
{"飘浮", "levitation"}, {"霉运", "unluck"}, {"黑暗", "darkness"},
|
||||
{"蓄风", "wind_charged"}, {"盘丝", "weaving"}, {"渗浆", "oozing"}, {"寄生", "infested"}
|
||||
};
|
||||
p.sendMessage(Component.text("§c§l负面效果:"));
|
||||
for (String[] eff : badList) {
|
||||
boolean alreadyExists = false;
|
||||
for (String[] be : land.badEffects) {
|
||||
if (be[0].equals(eff[0])) { alreadyExists = true; break; }
|
||||
}
|
||||
String prefix = alreadyExists ? "§7" : "§c";
|
||||
String suffix = alreadyExists ? " §7(已添加)" : "";
|
||||
Component btn = Component.text(prefix + "§l[+] " + eff[0] + suffix);
|
||||
if (!alreadyExists) {
|
||||
btn = btn.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"§e点击添加负面效果: " + eff[0] + " Lv1 300秒"
|
||||
+ " §7(等级上限 Lv" + areaProtect.getEffectLevelCap(eff[0], land.owner) + ")")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsbadadd " + landName + " " + eff[0] + " 1 300"));
|
||||
}
|
||||
p.sendMessage(btn);
|
||||
}
|
||||
|
||||
String[][] neutralList = {
|
||||
{"不祥之兆", "bad_omen"}, {"袭击之兆", "raid_omen"}, {"试炼之兆", "trial_omen"}
|
||||
};
|
||||
p.sendMessage(Component.text(""));
|
||||
p.sendMessage(Component.text("§e§l中性效果(也算负面档):"));
|
||||
for (String[] eff : neutralList) {
|
||||
boolean alreadyExists = false;
|
||||
for (String[] be : land.badEffects) {
|
||||
if (be[0].equals(eff[0])) { alreadyExists = true; break; }
|
||||
}
|
||||
String prefix = alreadyExists ? "§7" : "§e";
|
||||
String suffix = alreadyExists ? " §7(已添加)" : "";
|
||||
Component btn = Component.text(prefix + "§l[+] " + eff[0] + suffix);
|
||||
if (!alreadyExists) {
|
||||
btn = btn.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"§e点击添加负面效果: " + eff[0] + " Lv1 300秒"
|
||||
+ " §7(等级上限 Lv" + areaProtect.getEffectLevelCap(eff[0], land.owner) + ")")))
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsbadadd " + landName + " " + eff[0] + " 1 300"));
|
||||
}
|
||||
p.sendMessage(btn);
|
||||
}
|
||||
|
||||
p.sendMessage(Component.text(""));
|
||||
p.sendMessage(Component.text("§a[◀ 返回负面效果列表]")
|
||||
.clickEvent(ClickEvent.runCommand("/protect cli effectsmgmt " + landName + " 6")));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1668,10 +1771,11 @@ public class AreaCLIManager {
|
||||
}
|
||||
|
||||
// ★ 数量上限(2026-10-04 任务3):普通玩家 5 个,用户组可覆盖
|
||||
// 2026-10-04 负面拆出后按「增益+负面合计」判,避免两边各占满一份
|
||||
int maxEff = areaProtect.getMaxGiveEffects(land.owner);
|
||||
if (land.giveEffects.size() >= maxEff) {
|
||||
p.sendMessage(Component.text("§c该领地增益效果已达上限 "
|
||||
+ maxEff + " 个(按用户组配置),请先移除再添加"));
|
||||
if (land.giveEffects.size() + land.badEffects.size() >= maxEff) {
|
||||
p.sendMessage(Component.text("§c效果已达上限 " + maxEff
|
||||
+ " 个(增益+负面合计,按用户组配置),请先移除再添加"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1681,6 +1785,93 @@ public class AreaCLIManager {
|
||||
areaProtect.saveAreaToDb(land);
|
||||
}
|
||||
|
||||
// ========== 负面效果(2026-10-04 从增益列表拆出,CLI 与 GUI 同步) ==========
|
||||
|
||||
/**
|
||||
* 从负面效果列表中移除指定效果
|
||||
*/
|
||||
public void removeBadEffect(Player p, String landName, int index) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
return;
|
||||
}
|
||||
if (index < 1 || index > land.badEffects.size()) {
|
||||
p.sendMessage(Component.text("§c索引超出范围"));
|
||||
return;
|
||||
}
|
||||
String[] eff = land.badEffects.remove(index - 1);
|
||||
String desc = eff[0] + (eff.length > 1 ? " Lv" + eff[1] : "");
|
||||
p.sendMessage(Component.text("§a已移除负面效果: §f" + desc));
|
||||
areaProtect.saveAreaToDb(land);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向负面效果列表添加效果
|
||||
* 参数: 效果名 [等级] [秒数]
|
||||
*
|
||||
* <p>与 {@link #addGiveEffect} 的区别:写入 {@code badEffects},且数量上限
|
||||
* 与增益<b>合计</b>计算(用户组 max_effects 是同一份预算)。</p>
|
||||
*/
|
||||
public void addBadEffect(Player p, String landName, String effName, String levelStr, String durationStr) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) {
|
||||
p.sendMessage(Component.text("§c领地不存在: " + landName));
|
||||
return;
|
||||
}
|
||||
int level = 1;
|
||||
try { level = Integer.parseInt(levelStr); } catch (Exception ignored) {}
|
||||
level = Math.max(1, Math.min(level, 255));
|
||||
|
||||
int duration = 999;
|
||||
try { duration = Integer.parseInt(durationStr); } catch (Exception ignored) {}
|
||||
duration = Math.max(1, Math.min(duration, 3600));
|
||||
|
||||
for (String[] existing : land.badEffects) {
|
||||
if (existing[0].equalsIgnoreCase(effName)) {
|
||||
p.sendMessage(Component.text("§c该效果已存在于负面列表中: §f" + existing[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 数量上限:增益 + 负面合计(用户组 max_effects)
|
||||
int maxEff = areaProtect.getMaxGiveEffects(land.owner);
|
||||
if (land.giveEffects.size() + land.badEffects.size() >= maxEff) {
|
||||
p.sendMessage(Component.text("§c效果已达上限 " + maxEff
|
||||
+ " 个(增益+负面合计,按用户组配置),请先移除再添加"));
|
||||
return;
|
||||
}
|
||||
|
||||
land.badEffects.add(new String[]{effName, String.valueOf(level), String.valueOf(duration)});
|
||||
String desc = effName + " Lv" + level + " " + duration + "秒";
|
||||
p.sendMessage(Component.text("§a已添加负面效果: §f" + desc
|
||||
+ " §7(等级上限 Lv" + areaProtect.getEffectLevelCap(effName, land.owner) + ")"));
|
||||
areaProtect.saveAreaToDb(land);
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 开始编辑负面效果的等级或时长(等待玩家输入)
|
||||
*/
|
||||
public void startEditBadEffect(Player p, String landName, String indexStr, String editField) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) { p.sendMessage(Component.text("§c领地不存在")); return; }
|
||||
int idx;
|
||||
try { idx = Integer.parseInt(indexStr); } catch (Exception e) { p.sendMessage(Component.text("§c序号无效")); return; }
|
||||
if (idx < 1 || idx > land.badEffects.size()) { p.sendMessage(Component.text("§c序号超出范围")); return; }
|
||||
if (!editField.equals("level") && !editField.equals("duration")) {
|
||||
p.sendMessage(Component.text("§c编辑类型必须是 level 或 duration"));
|
||||
return;
|
||||
}
|
||||
// inputType 前缀用 editBad_,AreaProtection 侧按前缀分发回写
|
||||
String inputType = "editBad_" + editField + "_" + idx;
|
||||
areaProtect.setPendingEffectInput(p.getUniqueId(), landName, inputType, 6);
|
||||
String fieldLabel = editField.equals("level") ? "等级" : "时长(秒)";
|
||||
String[] eff = land.badEffects.get(idx - 1);
|
||||
String current = editField.equals("level") ? (eff.length > 1 ? eff[1] : "1") : (eff.length > 2 ? eff[2] : "300");
|
||||
p.sendMessage(Component.text("§e请输入新的" + fieldLabel + "(当前: " + current + "),输入取消"));
|
||||
p.sendMessage(Component.text("§7§l───────────────────────────────"));
|
||||
}
|
||||
|
||||
// ==================== 领地公告管理 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -1214,14 +1214,16 @@ public class AreaGUIManager implements Listener {
|
||||
}
|
||||
|
||||
// 效果管理
|
||||
if (title.startsWith("§b§l效果管理") || title.startsWith("§b§l单清效果") || title.startsWith("§b§l增益效果")) {
|
||||
if (title.startsWith("§b§l效果管理") || title.startsWith("§b§l单清效果")
|
||||
|| title.startsWith("§b§l增益效果") || title.startsWith("§c§l负面效果")) {
|
||||
event.setCancelled(true);
|
||||
String landName = managingLand.get(p.getUniqueId());
|
||||
if (landName == null) return;
|
||||
|
||||
// 从标题提取subPage
|
||||
// 从标题提取subPage(注意:负面效果标题含"效果"但不含"单清",先判长的)
|
||||
int subPage = 1;
|
||||
if (title.contains("单清效果")) subPage = 2;
|
||||
if (title.contains("负面效果")) subPage = 4;
|
||||
else if (title.contains("单清效果")) subPage = 2;
|
||||
else if (title.contains("增益效果")) subPage = 3;
|
||||
|
||||
handleEffectsManagementClick(p, raw, subPage, landName);
|
||||
@@ -1496,8 +1498,9 @@ public class AreaGUIManager implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// 效果选择列表
|
||||
if (title.startsWith("§a§l选择效果 - ")) {
|
||||
// 效果选择列表(增益 / 负面 / 单清 三种标题)
|
||||
if (title.startsWith("§a§l选择效果 - ") || title.startsWith("§a§l选择增益效果 - ")
|
||||
|| title.startsWith("§a§l选择负面效果 - ")) {
|
||||
event.setCancelled(true);
|
||||
EffectSelectionState state = pendingEffectSelection.get(p.getUniqueId());
|
||||
if (state == null) return;
|
||||
@@ -1511,8 +1514,8 @@ public class AreaGUIManager implements Listener {
|
||||
|
||||
if (raw < 0 || raw >= 45) return;
|
||||
|
||||
// 获取效果名
|
||||
String effName = getEffectNameBySlot(raw);
|
||||
// 获取效果名(★ 必须按 type 走对应档位的槽位映射)
|
||||
String effName = getEffectNameBySlot(raw, state.type);
|
||||
if (effName == null) return;
|
||||
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(state.landName);
|
||||
@@ -1527,6 +1530,28 @@ public class AreaGUIManager implements Listener {
|
||||
} else {
|
||||
p.sendMessage("§c§l[效果管理] §f该效果已在清除列表中: §e" + effName);
|
||||
}
|
||||
} else if ("bad".equals(state.type)) {
|
||||
// 添加到负面效果列表(2026-10-04 从增益拆出)
|
||||
boolean alreadyExists = false;
|
||||
for (String[] be : land.badEffects) {
|
||||
if (be[0].equals(effName)) { alreadyExists = true; break; }
|
||||
}
|
||||
if (!alreadyExists) {
|
||||
// 负面效果与增益共用数量上限口径(都受用户组 max_effects 约束)
|
||||
int maxEff = areaProtect.getMaxGiveEffects(land.owner);
|
||||
if (land.giveEffects.size() + land.badEffects.size() >= maxEff) {
|
||||
p.sendMessage("§c§l[效果管理] §f效果已达上限 §e"
|
||||
+ maxEff + " §f个(增益+负面合计,按用户组配置),请先移除再添加");
|
||||
} else {
|
||||
int cap = areaProtect.getEffectLevelCap(effName, land.owner);
|
||||
land.badEffects.add(new String[]{effName, "1", "300"});
|
||||
areaProtect.saveAreaToDb(land);
|
||||
p.sendMessage("§c§l[效果管理] §f已添加负面效果: §c" + effName + " Lv1 300秒"
|
||||
+ " §7(等级上限 Lv" + cap + ")");
|
||||
}
|
||||
} else {
|
||||
p.sendMessage("§c§l[效果管理] §f该负面效果已存在: §e" + effName);
|
||||
}
|
||||
} else {
|
||||
// 添加到增益效果列表
|
||||
boolean alreadyExists = false;
|
||||
@@ -1536,13 +1561,15 @@ public class AreaGUIManager implements Listener {
|
||||
if (!alreadyExists) {
|
||||
// ★ 数量上限(2026-10-04 任务3)
|
||||
int maxEff = areaProtect.getMaxGiveEffects(land.owner);
|
||||
if (land.giveEffects.size() >= maxEff) {
|
||||
p.sendMessage("§c§l[效果管理] §f增益效果已达上限 §e"
|
||||
+ maxEff + " §f个(按用户组配置),请先移除再添加");
|
||||
if (land.giveEffects.size() + land.badEffects.size() >= maxEff) {
|
||||
p.sendMessage("§c§l[效果管理] §f效果已达上限 §e"
|
||||
+ maxEff + " §f个(增益+负面合计,按用户组配置),请先移除再添加");
|
||||
} else {
|
||||
int cap = areaProtect.getEffectLevelCap(effName, land.owner);
|
||||
land.giveEffects.add(new String[]{effName, "1", "300"});
|
||||
areaProtect.saveAreaToDb(land);
|
||||
p.sendMessage("§a§l[效果管理] §f已添加增益效果: §a" + effName + " Lv1 300秒");
|
||||
p.sendMessage("§a§l[效果管理] §f已添加增益效果: §a" + effName + " Lv1 300秒"
|
||||
+ " §7(等级上限 Lv" + cap + ")");
|
||||
}
|
||||
} else {
|
||||
p.sendMessage("§c§l[效果管理] §f该增益效果已存在: §e" + effName);
|
||||
@@ -1557,31 +1584,39 @@ public class AreaGUIManager implements Listener {
|
||||
|
||||
/**
|
||||
* 根据槽位获取效果名
|
||||
*
|
||||
* <p>槽位编排必须和 {@link #openEffectsSelection} 里实际画出来的顺序<b>完全一致</b>,
|
||||
* 否则会出现「点了 A 名字却存了 B」。两类页面各按自己的档位顺序算:</p>
|
||||
* <ul>
|
||||
* <li>单清 / 负面:负面 → 中性 → 正面(负面页只画前两段)</li>
|
||||
* <li>增益:只画正面一段,槽位从 0 开始就是正面第 1 个</li>
|
||||
* </ul>
|
||||
*/
|
||||
private String getEffectNameBySlot(int slot) {
|
||||
String[][] allEffects = {
|
||||
// 负面效果 (0-15)
|
||||
{"缓慢", "slowness"}, {"挖掘疲劳", "mining_fatigue"}, {"瞬间伤害", "instant_damage"},
|
||||
{"反胃", "nausea"}, {"失明", "blindness"}, {"饥饿", "hunger"},
|
||||
{"虚弱", "weakness"}, {"中毒", "poison"}, {"凋零", "wither"},
|
||||
{"飘浮", "levitation"}, {"霉运", "unluck"}, {"黑暗", "darkness"},
|
||||
{"蓄风", "wind_charged"}, {"盘丝", "weaving"}, {"渗浆", "oozing"}, {"寄生", "infested"},
|
||||
// 中性效果 (16-18)
|
||||
{"不祥之兆", "bad_omen"}, {"袭击之兆", "raid_omen"}, {"试炼之兆", "trial_omen"},
|
||||
// 正面效果 (19-38)
|
||||
{"迅捷", "speed"}, {"急迫", "haste"}, {"力量", "strength"},
|
||||
{"瞬间治疗", "instant_health"}, {"跳跃提升", "jump_boost"}, {"生命恢复", "regeneration"},
|
||||
{"抗性提升", "resistance"}, {"抗火", "fire_resistance"}, {"水下呼吸", "water_breathing"},
|
||||
{"隐身", "invisibility"}, {"夜视", "night_vision"}, {"发光", "glowing"},
|
||||
{"生命提升", "health_boost"}, {"伤害吸收", "absorption"}, {"饱和", "saturation"},
|
||||
{"幸运", "luck"}, {"村庄英雄", "hero_of_the_village"}, {"缓降", "slow_falling"},
|
||||
{"潮涌能量", "conduit_power"}, {"海豚的恩惠", "dolphins_grace"}
|
||||
};
|
||||
private String getEffectNameBySlot(int slot, String type) {
|
||||
boolean isGive = "give".equals(type);
|
||||
boolean isBad = "bad".equals(type);
|
||||
|
||||
if (slot >= 0 && slot < allEffects.length) {
|
||||
return allEffects[slot][0];
|
||||
if (isGive) {
|
||||
if (slot >= 0 && slot < EFFECTS_GOOD.length) return EFFECTS_GOOD[slot][0];
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
if (isBad) {
|
||||
// 负面页:负面(16) + 中性(3)
|
||||
int total = EFFECTS_BAD.length + EFFECTS_NEUTRAL.length;
|
||||
if (slot >= 0 && slot < total) {
|
||||
if (slot < EFFECTS_BAD.length) return EFFECTS_BAD[slot][0];
|
||||
return EFFECTS_NEUTRAL[slot - EFFECTS_BAD.length][0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 单清:三种全列,顺序与 openEffectsSelection 一致
|
||||
int nBad = EFFECTS_BAD.length;
|
||||
int nNeu = EFFECTS_NEUTRAL.length;
|
||||
int total = nBad + nNeu + EFFECTS_GOOD.length;
|
||||
if (slot < 0 || slot >= total) return null;
|
||||
if (slot < nBad) return EFFECTS_BAD[slot][0];
|
||||
if (slot < nBad + nNeu) return EFFECTS_NEUTRAL[slot - nBad][0];
|
||||
return EFFECTS_GOOD[slot - nBad - nNeu][0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2442,6 +2477,37 @@ public class AreaGUIManager implements Listener {
|
||||
|
||||
// ==================== 效果管理 GUI ====================
|
||||
|
||||
/**
|
||||
* ★ 效果清单唯一来源(2026-10-04 用户要求拆分后集中)
|
||||
*
|
||||
* <p>此前 {@code openEffectsSelection} 与 {@code getEffectNameBySlot} 各写了一份数组,
|
||||
* 两份一旦漂移就会出现「点了没反应 / 槽位对不上」。现统一到这两个常量。</p>
|
||||
*/
|
||||
/** 负面效果:缓慢、中毒、凋零这类会拖垮玩家的效果(用户要求单独一个子菜单)。 */
|
||||
private static final String[][] EFFECTS_BAD = {
|
||||
{"缓慢", "slowness"}, {"挖掘疲劳", "mining_fatigue"}, {"瞬间伤害", "instant_damage"},
|
||||
{"反胃", "nausea"}, {"失明", "blindness"}, {"饥饿", "hunger"},
|
||||
{"虚弱", "weakness"}, {"中毒", "poison"}, {"凋零", "wither"},
|
||||
{"飘浮", "levitation"}, {"霉运", "unluck"}, {"黑暗", "darkness"},
|
||||
{"蓄风", "wind_charged"}, {"盘丝", "weaving"}, {"渗浆", "oozing"}, {"寄生", "infested"}
|
||||
};
|
||||
|
||||
/** 中性效果:不吉不利也不算增益,单列一档。 */
|
||||
private static final String[][] EFFECTS_NEUTRAL = {
|
||||
{"不祥之兆", "bad_omen"}, {"袭击之兆", "raid_omen"}, {"试炼之兆", "trial_omen"}
|
||||
};
|
||||
|
||||
/** 增益效果:全部正面效果(用户要求「增益效果只归纳增益」)。 */
|
||||
private static final String[][] EFFECTS_GOOD = {
|
||||
{"迅捷", "speed"}, {"急迫", "haste"}, {"力量", "strength"},
|
||||
{"瞬间治疗", "instant_health"}, {"跳跃提升", "jump_boost"}, {"生命恢复", "regeneration"},
|
||||
{"抗性提升", "resistance"}, {"抗火", "fire_resistance"}, {"水下呼吸", "water_breathing"},
|
||||
{"隐身", "invisibility"}, {"夜视", "night_vision"}, {"发光", "glowing"},
|
||||
{"生命提升", "health_boost"}, {"伤害吸收", "absorption"}, {"饱和", "saturation"},
|
||||
{"幸运", "luck"}, {"村庄英雄", "hero_of_the_village"}, {"缓降", "slow_falling"},
|
||||
{"潮涌能量", "conduit_power"}, {"海豚的恩惠", "dolphins_grace"}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开效果管理菜单
|
||||
*/
|
||||
@@ -2458,7 +2524,10 @@ public class AreaGUIManager implements Listener {
|
||||
if (subPage == 1) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, "§b§l效果管理 - " + landName);
|
||||
|
||||
// 全清负面效果
|
||||
// ★ 槽位布局(2026-10-04 修正):原实现 11/13 各被两个按钮占用,
|
||||
// 后写的「添加增益」「添加负面」把「清除所有负面」「禁止所有效果」直接覆盖掉了。
|
||||
// 现在 11~15 连排五格,与 handleEffectsManagementClick 的分发一一对应。
|
||||
// 11 全清负面效果
|
||||
Material clearAllMat = land.clearAllBadEffects ? Material.LIME_DYE : Material.GRAY_DYE;
|
||||
inv.setItem(11, createItem(clearAllMat, "§e§l清除所有负面效果",
|
||||
"§7当前: " + (land.clearAllBadEffects ? "§a已开启" : "§c已关闭"),
|
||||
@@ -2466,23 +2535,34 @@ public class AreaGUIManager implements Listener {
|
||||
"",
|
||||
"§e点击切换"));
|
||||
|
||||
// 禁止所有效果
|
||||
// 12 禁止所有效果
|
||||
Material denyAllMat = land.denyAllEffects ? Material.RED_DYE : Material.GRAY_DYE;
|
||||
inv.setItem(13, createItem(denyAllMat, "§e§l禁止所有效果",
|
||||
inv.setItem(12, createItem(denyAllMat, "§e§l禁止所有效果",
|
||||
"§7当前: " + (land.denyAllEffects ? "§a已开启" : "§c已关闭"),
|
||||
"§7进入领地时禁止接收所有药水效果",
|
||||
"",
|
||||
"§e点击切换"));
|
||||
|
||||
// 单清效果列表
|
||||
inv.setItem(15, createItem(Material.MAGMA_CREAM, "§b§l单清指定效果",
|
||||
// 13 单清指定效果
|
||||
inv.setItem(13, createItem(Material.MAGMA_CREAM, "§b§l单清指定效果",
|
||||
"§7当前列表: §f" + land.clearEffects.size() + " 个效果",
|
||||
"§7进入领地时清除玩家身上的这些效果",
|
||||
"",
|
||||
"§e点击查看/编辑"));
|
||||
|
||||
// 添加增益效果
|
||||
inv.setItem(17, createItem(Material.BREWING_STAND, "§a§l添加增益效果",
|
||||
// 14 添加增益效果(只放正面效果)
|
||||
inv.setItem(14, createItem(Material.BREWING_STAND, "§a§l添加增益效果",
|
||||
"§7当前增益: §f" + land.giveEffects.size() + " 个",
|
||||
"§7只列正面效果(力量、抗性、跳跃提升…)",
|
||||
"§7等级上限按原版生存可获取档位封顶",
|
||||
"",
|
||||
"§e点击查看/编辑"));
|
||||
|
||||
// 15 添加负面效果(独立子菜单,2026-10-04 用户要求拆出)
|
||||
inv.setItem(15, createItem(Material.WITHER_SKELETON_SKULL, "§c§l添加负面效果",
|
||||
"§7当前负面: §f" + land.badEffects.size() + " 个",
|
||||
"§7缓慢、中毒、凋零、虚弱、蓄风…",
|
||||
"§7含不祥之兆等中性效果,不与增益混排",
|
||||
"",
|
||||
"§e点击查看/编辑"));
|
||||
|
||||
@@ -2517,7 +2597,7 @@ public class AreaGUIManager implements Listener {
|
||||
p.openInventory(inv);
|
||||
}
|
||||
|
||||
// ========== 子菜单3:增益效果列表 ==========
|
||||
// ========== 子菜单3:增益效果列表(只列正面效果) ==========
|
||||
else if (subPage == 3) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, "§b§l增益效果 - " + landName);
|
||||
|
||||
@@ -2532,33 +2612,8 @@ public class AreaGUIManager implements Listener {
|
||||
String desc = eff[0] + (eff.length > 1 ? " Lv" + eff[1] : "") + (eff.length > 2 ? " " + eff[2] + "秒" : "");
|
||||
inv.setItem(i, createItem(Material.GOLDEN_CARROT, "§a" + desc,
|
||||
"§7点击移除此增益效果",
|
||||
"§e序号: " + (i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setItem(48, createItem(Material.ARROW, "§c§l返回效果管理", ""));
|
||||
inv.setItem(53, createItem(Material.BOOK, "§a§l添加效果",
|
||||
"§7点击选择要清除的效果"));
|
||||
|
||||
p.openInventory(inv);
|
||||
}
|
||||
|
||||
// ========== 子菜单3:增益效果列表 ==========
|
||||
else if (subPage == 3) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, "§b§l增益效果 - " + landName);
|
||||
|
||||
if (land.giveEffects.isEmpty()) {
|
||||
inv.setItem(22, createItem(Material.BARRIER, "§7§l暂无增益效果",
|
||||
"§7使用快捷指令添加: /protect cli effectsaddadd <效果名> [等级] [秒数]",
|
||||
"",
|
||||
"§e示例: /protect cli effectsaddadd 力量 2 300"));
|
||||
} else {
|
||||
for (int i = 0; i < Math.min(land.giveEffects.size(), 36); i++) {
|
||||
String[] eff = land.giveEffects.get(i);
|
||||
String desc = eff[0] + (eff.length > 1 ? " Lv" + eff[1] : "") + (eff.length > 2 ? " " + eff[2] + "秒" : "");
|
||||
inv.setItem(i, createItem(Material.GOLDEN_CARROT, "§a" + desc,
|
||||
"§7点击移除此增益效果",
|
||||
"§e序号: " + (i + 1)));
|
||||
"§e序号: " + (i + 1),
|
||||
"§7等级上限: §fLv" + areaProtect.getEffectLevelCap(eff[0], land.owner)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2568,6 +2623,33 @@ public class AreaGUIManager implements Listener {
|
||||
|
||||
p.openInventory(inv);
|
||||
}
|
||||
|
||||
// ========== 子菜单4:负面效果列表(2026-10-04 从增益里拆出) ==========
|
||||
else if (subPage == 4) {
|
||||
Inventory inv = Bukkit.createInventory(null, 54, "§c§l负面效果 - " + landName);
|
||||
|
||||
if (land.badEffects.isEmpty()) {
|
||||
inv.setItem(22, createItem(Material.BARRIER, "§7§l暂无负面效果",
|
||||
"§7使用快捷指令添加: /protect cli effectsbadadd <效果名> [等级] [秒数]",
|
||||
"",
|
||||
"§e示例: /protect cli effectsbadadd 缓慢 1 300"));
|
||||
} else {
|
||||
for (int i = 0; i < Math.min(land.badEffects.size(), 36); i++) {
|
||||
String[] eff = land.badEffects.get(i);
|
||||
String desc = eff[0] + (eff.length > 1 ? " Lv" + eff[1] : "") + (eff.length > 2 ? " " + eff[2] + "秒" : "");
|
||||
inv.setItem(i, createItem(Material.WITHER_SKELETON_SKULL, "§c" + desc,
|
||||
"§7点击移除此负面效果",
|
||||
"§e序号: " + (i + 1),
|
||||
"§7等级上限: §fLv" + areaProtect.getEffectLevelCap(eff[0], land.owner)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setItem(48, createItem(Material.ARROW, "§c§l返回效果管理", ""));
|
||||
inv.setItem(53, createItem(Material.BOOK, "§c§l添加负面",
|
||||
"§7点击选择要添加的负面效果"));
|
||||
|
||||
p.openInventory(inv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2584,17 +2666,20 @@ public class AreaGUIManager implements Listener {
|
||||
land.clearAllBadEffects = !land.clearAllBadEffects;
|
||||
areaProtect.saveAreaToDb(land);
|
||||
openEffectsManagement(p, landName, 1);
|
||||
} else if (raw == 13) {
|
||||
} else if (raw == 12) {
|
||||
// 切换禁止所有效果
|
||||
land.denyAllEffects = !land.denyAllEffects;
|
||||
areaProtect.saveAreaToDb(land);
|
||||
openEffectsManagement(p, landName, 1);
|
||||
} else if (raw == 15) {
|
||||
} else if (raw == 13) {
|
||||
// 打开单清效果列表
|
||||
openEffectsManagement(p, landName, 2);
|
||||
} else if (raw == 17) {
|
||||
// 打开增益效果列表
|
||||
} else if (raw == 14) {
|
||||
// 打开增益效果列表(只列正面效果)
|
||||
openEffectsManagement(p, landName, 3);
|
||||
} else if (raw == 15) {
|
||||
// 打开负面效果列表(2026-10-04 从增益拆出)
|
||||
openEffectsManagement(p, landName, 4);
|
||||
} else if (raw == 48) {
|
||||
// 返回管理领地
|
||||
openLandManage(p, landName);
|
||||
@@ -2627,77 +2712,104 @@ public class AreaGUIManager implements Listener {
|
||||
} else if (raw == 48) {
|
||||
openEffectsManagement(p, landName, 1);
|
||||
} else if (raw == 53) {
|
||||
// 打开效果选择列表(与单清一致)
|
||||
// 打开增益效果选择列表(只列正面效果)
|
||||
openEffectsSelection(p, landName, "give", 3);
|
||||
}
|
||||
}
|
||||
// 子菜单4:负面效果列表(2026-10-04 从增益拆出)
|
||||
else if (subPage == 4) {
|
||||
if (raw < land.badEffects.size()) {
|
||||
// 移除负面效果
|
||||
String[] removed = land.badEffects.remove(raw);
|
||||
String desc = removed[0] + (removed.length > 1 ? " Lv" + removed[1] : "") + (removed.length > 2 ? " " + removed[2] + "秒" : "");
|
||||
areaProtect.saveAreaToDb(land);
|
||||
p.sendMessage("§c§l[效果管理] §f已移除负面效果: §c" + desc);
|
||||
openEffectsManagement(p, landName, 4);
|
||||
} else if (raw == 48) {
|
||||
openEffectsManagement(p, landName, 1);
|
||||
} else if (raw == 53) {
|
||||
// 打开负面效果选择列表
|
||||
openEffectsSelection(p, landName, "bad", 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开效果选择列表(可点击选择)
|
||||
* @param type "clear"=清除效果, "give"=增益效果
|
||||
*
|
||||
* <p>2026-10-04 用户要求:增益只列增益、负面单独一个子菜单,
|
||||
* 原来三种混排在一页里,加错效果很难发现。现按 {@code type} 分类只列对应档位。</p>
|
||||
*
|
||||
* @param type "clear"=单清指定效果(负面+中性+正面全列)、
|
||||
* "give"=增益效果(只列正面)、
|
||||
* "bad"=负面效果(只列负面+中性)
|
||||
* @param returnSubPage 返回时的子菜单页码
|
||||
*/
|
||||
private void openEffectsSelection(Player p, String landName, String type, int returnSubPage) {
|
||||
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
|
||||
if (land == null) return;
|
||||
|
||||
String title = "§a§l选择效果 - " + landName;
|
||||
String title;
|
||||
if ("bad".equals(type)) {
|
||||
title = "§a§l选择负面效果 - " + landName;
|
||||
} else if ("give".equals(type)) {
|
||||
title = "§a§l选择增益效果 - " + landName;
|
||||
} else {
|
||||
title = "§a§l选择效果 - " + landName;
|
||||
}
|
||||
Inventory inv = Bukkit.createInventory(null, 54, title);
|
||||
|
||||
// 负面效果
|
||||
String[][] badEffects = {
|
||||
{"缓慢", "slowness"}, {"挖掘疲劳", "mining_fatigue"}, {"瞬间伤害", "instant_damage"},
|
||||
{"反胃", "nausea"}, {"失明", "blindness"}, {"饥饿", "hunger"},
|
||||
{"虚弱", "weakness"}, {"中毒", "poison"}, {"凋零", "wither"},
|
||||
{"飘浮", "levitation"}, {"霉运", "unluck"}, {"黑暗", "darkness"},
|
||||
{"蓄风", "wind_charged"}, {"盘丝", "weaving"}, {"渗浆", "oozing"}, {"寄生", "infested"}
|
||||
};
|
||||
|
||||
// 中性效果
|
||||
String[][] neutralEffects = {
|
||||
{"不祥之兆", "bad_omen"}, {"袭击之兆", "raid_omen"}, {"试炼之兆", "trial_omen"}
|
||||
};
|
||||
|
||||
// 正面效果
|
||||
String[][] goodEffects = {
|
||||
{"迅捷", "speed"}, {"急迫", "haste"}, {"力量", "strength"},
|
||||
{"瞬间治疗", "instant_health"}, {"跳跃提升", "jump_boost"}, {"生命恢复", "regeneration"},
|
||||
{"抗性提升", "resistance"}, {"抗火", "fire_resistance"}, {"水下呼吸", "water_breathing"},
|
||||
{"隐身", "invisibility"}, {"夜视", "night_vision"}, {"发光", "glowing"},
|
||||
{"生命提升", "health_boost"}, {"伤害吸收", "absorption"}, {"饱和", "saturation"},
|
||||
{"幸运", "luck"}, {"村庄英雄", "hero_of_the_village"}, {"缓降", "slow_falling"},
|
||||
{"潮涌能量", "conduit_power"}, {"海豚的恩惠", "dolphins_grace"}
|
||||
};
|
||||
// 按用途决定列出哪些档位
|
||||
boolean isClear = "clear".equals(type);
|
||||
boolean isGive = "give".equals(type);
|
||||
boolean isBad = "bad".equals(type);
|
||||
// 单清:三种都列(这是"移除指定效果",语义与增益/负面无关,保持原样)
|
||||
// 增益:只列正面;负面:列负面+中性
|
||||
boolean showBad = isClear || isBad;
|
||||
boolean showNeutral = isClear || isBad;
|
||||
boolean showGood = isClear || isGive;
|
||||
|
||||
int slot = 0;
|
||||
String[][] badEffects = showBad ? EFFECTS_BAD : new String[0][];
|
||||
String[][] neutralEffects = showNeutral ? EFFECTS_NEUTRAL : new String[0][];
|
||||
String[][] goodEffects = showGood ? EFFECTS_GOOD : new String[0][];
|
||||
|
||||
// 负面效果
|
||||
for (String[] eff : badEffects) {
|
||||
if (slot >= 45) break;
|
||||
boolean alreadyInList = type.equals("clear") && land.clearEffects.contains(eff[0]);
|
||||
boolean alreadyInList = isClear && land.clearEffects.contains(eff[0]);
|
||||
Material mat = alreadyInList ? Material.GRAY_DYE : Material.RED_DYE;
|
||||
String name = alreadyInList ? "§7" + eff[0] + " (已添加)" : "§c" + eff[0];
|
||||
inv.setItem(slot, createItem(mat, name, "§7点击添加到清除列表", "§e英文名: " + eff[1]));
|
||||
inv.setItem(slot, createItem(mat, name,
|
||||
isClear ? "§7点击添加到清除列表" : "§7点击添加到负面效果列表",
|
||||
"§e英文名: " + eff[1],
|
||||
"§7等级上限: §fLv" + areaProtect.getEffectLevelCap(eff[0], land.owner)));
|
||||
slot++;
|
||||
}
|
||||
|
||||
// 中性效果
|
||||
for (String[] eff : neutralEffects) {
|
||||
if (slot >= 45) break;
|
||||
boolean alreadyInList = type.equals("clear") && land.clearEffects.contains(eff[0]);
|
||||
boolean alreadyInList = isClear && land.clearEffects.contains(eff[0]);
|
||||
Material mat = alreadyInList ? Material.GRAY_DYE : Material.YELLOW_DYE;
|
||||
String name = alreadyInList ? "§7" + eff[0] + " (已添加)" : "§e" + eff[0];
|
||||
inv.setItem(slot, createItem(mat, name, "§7点击添加到清除列表", "§e英文名: " + eff[1]));
|
||||
inv.setItem(slot, createItem(mat, name,
|
||||
isClear ? "§7点击添加到清除列表" : "§7点击添加到负面效果列表",
|
||||
"§e英文名: " + eff[1],
|
||||
"§7等级上限: §fLv" + areaProtect.getEffectLevelCap(eff[0], land.owner)));
|
||||
slot++;
|
||||
}
|
||||
|
||||
// 正面效果
|
||||
for (String[] eff : goodEffects) {
|
||||
if (slot >= 45) break;
|
||||
boolean alreadyInList = type.equals("clear") && land.clearEffects.contains(eff[0]);
|
||||
boolean alreadyInList = isClear && land.clearEffects.contains(eff[0]);
|
||||
Material mat = alreadyInList ? Material.GRAY_DYE : Material.LIME_DYE;
|
||||
String name = alreadyInList ? "§7" + eff[0] + " (已添加)" : "§a" + eff[0];
|
||||
inv.setItem(slot, createItem(mat, name, "§7点击添加到清除列表", "§e英文名: " + eff[1]));
|
||||
inv.setItem(slot, createItem(mat, name,
|
||||
isClear ? "§7点击添加到清除列表" : "§7点击添加到增益效果列表",
|
||||
"§e英文名: " + eff[1],
|
||||
"§7等级上限: §fLv" + areaProtect.getEffectLevelCap(eff[0], land.owner)));
|
||||
slot++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1250,6 +1250,7 @@ public class AreaProtection implements Listener {
|
||||
try { ac.denySignEdit = rs.getInt("deny_sign_edit") == 1; } catch (Exception ignored) {}
|
||||
try { ac.denySpawnEgg = rs.getInt("deny_spawn_egg") == 1; } catch (Exception ignored) {}
|
||||
try { ac.denyWax = rs.getInt("deny_wax") == 1; } catch (Exception ignored) {}
|
||||
try { ac.badEffects = parseEffectsString(rs.getString("bad_effects")); } catch (Exception ignored) { ac.badEffects = new ArrayList<>(); }
|
||||
try { ac.warpX = rs.getDouble("warp_x"); } catch (Exception ignored) {}
|
||||
try { ac.warpY = rs.getDouble("warp_y"); } catch (Exception ignored) {}
|
||||
try { ac.warpZ = rs.getDouble("warp_z"); } catch (Exception ignored) {}
|
||||
@@ -1437,6 +1438,7 @@ public class AreaProtection implements Listener {
|
||||
+ "confiscate_items TEXT DEFAULT '',"
|
||||
+ "deny_use_items TEXT DEFAULT '',"
|
||||
+ "give_effects TEXT DEFAULT '',"
|
||||
+ "bad_effects TEXT DEFAULT '',"
|
||||
+ "clear_effects TEXT DEFAULT '',"
|
||||
+ "clear_all_bad INTEGER DEFAULT 0,"
|
||||
+ "punish_commands TEXT DEFAULT '',"
|
||||
@@ -1550,6 +1552,7 @@ public class AreaProtection implements Listener {
|
||||
try { stmt.executeUpdate("ALTER TABLE area_lands ADD COLUMN deny_spawn_egg INTEGER DEFAULT 0"); } catch (Exception ignored) {}
|
||||
// ★ 涂蜡/刮蜡权限列(上级权限:破坏方块/放置方块)
|
||||
try { stmt.executeUpdate("ALTER TABLE area_lands ADD COLUMN deny_wax INTEGER DEFAULT 0"); } catch (Exception ignored) {}
|
||||
try { stmt.executeUpdate("ALTER TABLE area_lands ADD COLUMN bad_effects TEXT DEFAULT ''"); } catch (Exception ignored) {}
|
||||
|
||||
// ★ 全局配置默认值
|
||||
try {
|
||||
@@ -3793,15 +3796,20 @@ public class AreaProtection implements Listener {
|
||||
+ " 效果配置数="
|
||||
+ (ac != null ? ac.giveEffects.size() : "null"));*/
|
||||
|
||||
if (ac == null || ac.giveEffects.isEmpty()) return;
|
||||
if (ac.giveEffects.isEmpty()) return;
|
||||
if (ac == null) return;
|
||||
// ★ 2026-10-04:负面配置拆出后,不能再只判 giveEffects 为空就返回
|
||||
boolean hasGive = ac.giveEffects != null && !ac.giveEffects.isEmpty();
|
||||
boolean hasBad = ac.badEffects != null && !ac.badEffects.isEmpty();
|
||||
if (!hasGive && !hasBad) return;
|
||||
// 查DB:该玩家在此区域是否已有效果记录
|
||||
List<String> dbEffects =
|
||||
getPlayerEffectNames(uid, areaName);
|
||||
|
||||
if (dbEffects.isEmpty()) {
|
||||
// 无记录 → 写入DB
|
||||
for (String[] eff : ac.giveEffects) {
|
||||
List<String[]> allForDb = new ArrayList<>(ac.giveEffects);
|
||||
if (ac.badEffects != null) allForDb.addAll(ac.badEffects);
|
||||
for (String[] eff : allForDb) {
|
||||
PotionEffectType t =
|
||||
resolveEffectType(eff[0]);
|
||||
if (t == null) continue;
|
||||
@@ -3819,8 +3827,11 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
|
||||
// 给效果 + 贴标
|
||||
// ★ 2026-10-04:负面配置已拆到 badEffects,两边都要给
|
||||
List<String[]> allCfg = new ArrayList<>(ac.giveEffects);
|
||||
if (ac.badEffects != null) allCfg.addAll(ac.badEffects);
|
||||
List<PotionEffectType> applied = new ArrayList<>();
|
||||
for (String[] eff : ac.giveEffects) {
|
||||
for (String[] eff : allCfg) {
|
||||
try {
|
||||
PotionEffectType type =
|
||||
resolveEffectType(eff[0]);
|
||||
@@ -3842,6 +3853,9 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 成就防作弊贴标:这批是插件给的效果,必须登记
|
||||
markPluginEffects(uid, applied);
|
||||
|
||||
// 贴标
|
||||
// 新代码(合并,不覆盖):
|
||||
List<PotionEffectType> existingMarks =
|
||||
@@ -3884,11 +3898,17 @@ public class AreaProtection implements Listener {
|
||||
lastEffectHotUpdate.put(uid, now);
|
||||
|
||||
// 领地无效果配置 → 跳过
|
||||
if (ac.giveEffects == null || ac.giveEffects.isEmpty()) return;
|
||||
// ★ 2026-10-04:负面配置拆出后,不能只判 giveEffects
|
||||
boolean hasGive = ac.giveEffects != null && !ac.giveEffects.isEmpty();
|
||||
boolean hasBad = ac.badEffects != null && !ac.badEffects.isEmpty();
|
||||
if (!hasGive && !hasBad) return;
|
||||
|
||||
// 1. 构建领地当前期望的效果集合
|
||||
// 1. 构建领地当前期望的效果集合(增益 + 负面)
|
||||
Map<PotionEffectType, int[]> expectedEffects = new LinkedHashMap<>();
|
||||
for (String[] eff : ac.giveEffects) {
|
||||
List<String[]> allForHot = new ArrayList<>();
|
||||
if (ac.giveEffects != null) allForHot.addAll(ac.giveEffects);
|
||||
if (ac.badEffects != null) allForHot.addAll(ac.badEffects);
|
||||
for (String[] eff : allForHot) {
|
||||
PotionEffectType type = resolveEffectType(eff[0]);
|
||||
if (type == null) continue;
|
||||
try {
|
||||
@@ -3906,6 +3926,7 @@ public class AreaProtection implements Listener {
|
||||
|
||||
// 3. 添加缺失的效果
|
||||
List<PotionEffectType> newMarked = new ArrayList<>(currentMarked);
|
||||
List<PotionEffectType> hotApplied = new ArrayList<>();
|
||||
for (Map.Entry<PotionEffectType, int[]> entry : expectedEffects.entrySet()) {
|
||||
PotionEffectType type = entry.getKey();
|
||||
int lv = entry.getValue()[0];
|
||||
@@ -3917,6 +3938,7 @@ public class AreaProtection implements Listener {
|
||||
if (existing != null && existing.getAmplifier() < lv) {
|
||||
// 等级提升 → 重新给效果
|
||||
p.addPotionEffect(new PotionEffect(type, dur, lv));
|
||||
hotApplied.add(type);
|
||||
plugin.getLogger().info("[防护-热更新] 等级提升: "
|
||||
+ type.getName() + " Lv" + (lv + 1)
|
||||
+ " 玩家=" + p.getName());
|
||||
@@ -3929,12 +3951,16 @@ public class AreaProtection implements Listener {
|
||||
if (existing == null || existing.getAmplifier() < lv) {
|
||||
p.addPotionEffect(new PotionEffect(type, dur, lv));
|
||||
newMarked.add(type);
|
||||
hotApplied.add(type);
|
||||
plugin.getLogger().info("[防护-热更新] 新增效果: "
|
||||
+ type.getName() + " Lv" + (lv + 1) + " " + dur / 20 + "s"
|
||||
+ " 玩家=" + p.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 成就防作弊贴标:热更新补上的效果同样是插件给的
|
||||
markPluginEffects(uid, hotApplied);
|
||||
|
||||
// 4. 移除多余的效果(领地不再给予的)
|
||||
List<PotionEffectType> toRemove = new ArrayList<>();
|
||||
for (PotionEffectType type : currentMarked) {
|
||||
@@ -3994,6 +4020,12 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 成就防作弊:离开领地后插件不再持有这些效果,标记要一并撤掉,
|
||||
// 否则玩家之后自己喝药水凑齐 34 种时会被误判成作弊。
|
||||
if (plugin.achievementEffectGuard != null) {
|
||||
plugin.achievementEffectGuard.clearPluginEffects(uid, null);
|
||||
}
|
||||
|
||||
// 确认清空后清DB
|
||||
removePlayerEffects(uid, areaName);
|
||||
plugin.getLogger().info(
|
||||
@@ -4034,10 +4066,18 @@ public class AreaProtection implements Listener {
|
||||
// 新区域的效果集合
|
||||
Set<PotionEffectType> newTypes = new HashSet<>();
|
||||
if (newAc != null) {
|
||||
for (String[] eff : newAc.giveEffects) {
|
||||
PotionEffectType t =
|
||||
resolveEffectType(eff[0]);
|
||||
if (t != null) newTypes.add(t);
|
||||
if (newAc.giveEffects != null) {
|
||||
for (String[] eff : newAc.giveEffects) {
|
||||
PotionEffectType t = resolveEffectType(eff[0]);
|
||||
if (t != null) newTypes.add(t);
|
||||
}
|
||||
}
|
||||
// ★ 2026-10-04:负面配置拆到 badEffects,跨区域比对必须一起算
|
||||
if (newAc.badEffects != null) {
|
||||
for (String[] eff : newAc.badEffects) {
|
||||
PotionEffectType t = resolveEffectType(eff[0]);
|
||||
if (t != null) newTypes.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4148,36 +4188,79 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEnter(Player p, AreaConfig ac) {
|
||||
UUID uid = p.getUniqueId();
|
||||
/**
|
||||
* ★ 贴标:把「这批效果是插件给的」登记到成就防作弊守卫。
|
||||
*
|
||||
* <p>2026-10-04 新增。原先只有 {@code playerMarkedEffects} 这份内部标记
|
||||
* (用于离场清理),现在额外同步一份到 {@link AchievementEffectGuard},
|
||||
* 后者据此撤销 potion 类原版成就(all_potions / all_effects)。</p>
|
||||
*/
|
||||
private void markPluginEffects(UUID uid, List<PotionEffectType> types) {
|
||||
if (plugin.achievementEffectGuard == null || uid == null || types == null || types.isEmpty()) return;
|
||||
try {
|
||||
plugin.achievementEffectGuard.markPluginEffects(uid, types);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
|
||||
// 清除负面效果
|
||||
clearBadEffects(p, ac);
|
||||
|
||||
// 给予效果
|
||||
/**
|
||||
* 给予领地配置的全部效果(增益 giveEffects + 负面 badEffects)
|
||||
*
|
||||
* <p>2026-10-04:负面效果从 {@code giveEffects} 拆到 {@code badEffects},
|
||||
* 这里是两个列表的唯一合流点,改任何一处施加点都要记得带上 {@code badEffects}。</p>
|
||||
*
|
||||
* @return 实际施加成功(已解析出类型)的效果类型列表,供贴标使用
|
||||
*/
|
||||
private List<PotionEffectType> giveRegionEffects(Player p, AreaConfig ac) {
|
||||
List<PotionEffectType> given = new ArrayList<>();
|
||||
for (String[] parts : ac.giveEffects) {
|
||||
if (ac == null) return given;
|
||||
if (ac.giveEffects != null) given.addAll(applyEffectList(p, ac, ac.giveEffects));
|
||||
if (ac.badEffects != null) given.addAll(applyEffectList(p, ac, ac.badEffects));
|
||||
return given;
|
||||
}
|
||||
|
||||
/**
|
||||
* 施加一份效果列表([名字, 等级, 秒数]),统一走等级封顶
|
||||
*/
|
||||
private List<PotionEffectType> applyEffectList(Player p, AreaConfig ac, List<String[]> list) {
|
||||
List<PotionEffectType> applied = new ArrayList<>();
|
||||
if (list == null) return applied;
|
||||
for (String[] parts : list) {
|
||||
if (parts == null || parts.length < 1 || parts[0] == null || parts[0].isEmpty()) continue;
|
||||
String effName = parts[0];
|
||||
int level = 1;
|
||||
int duration = 999;
|
||||
try { level = Integer.parseInt(parts[1]); }
|
||||
catch (NumberFormatException ignored) {}
|
||||
catch (Exception ignored) {}
|
||||
try { duration = Integer.parseInt(parts[2]); }
|
||||
catch (NumberFormatException ignored) {}
|
||||
catch (Exception ignored) {}
|
||||
// ★ 药效等级封顶(2026-10-04 任务4):按该效果的生存可获取上限 + 用户组配置
|
||||
level = clampEffectLevel(level, effName, ac.owner);
|
||||
PotionEffectType type = resolveEffectType(effName);
|
||||
if (type != null) {
|
||||
p.addPotionEffect(new PotionEffect(
|
||||
type, duration * 20, level - 1));
|
||||
given.add(type);
|
||||
applied.add(type);
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
private void handleEnter(Player p, AreaConfig ac) {
|
||||
UUID uid = p.getUniqueId();
|
||||
|
||||
// ★ 2026-10-04 关键顺序修正:负面配置拆到 badEffects 后,
|
||||
// clearBadEffects 会把 badEffects 里配的负面一并清掉。
|
||||
// 所以「先给效果、再按配置清负面」——否则新增的负面效果子菜单等于白配。
|
||||
List<PotionEffectType> given = giveRegionEffects(p, ac);
|
||||
|
||||
// 清除负面效果(clearAllBadEffects / peaceMode / clearEffects / denyRaid)
|
||||
clearBadEffects(p, ac);
|
||||
|
||||
// ★ 贴标:这些效果是插件给的,不是玩家自己喝出来的。
|
||||
// 成就防作弊(撤销 nether/all_effects 等)完全依赖这份标记。
|
||||
markPluginEffects(uid, given);
|
||||
|
||||
removePlayerEffects(uid, ac.name);
|
||||
if (!given.isEmpty()) {
|
||||
// savePlayerEffects(uid, p.getName(), ac.name, given);
|
||||
}
|
||||
|
||||
// 白名单检查(最先执行)
|
||||
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
|
||||
@@ -5271,7 +5354,10 @@ public class AreaProtection implements Listener {
|
||||
|
||||
|
||||
private void applyEffects(Player p, AreaConfig ac) {
|
||||
for (String[] parts : ac.giveEffects) {
|
||||
// ★ 2026-10-04:负面效果从 giveEffects 拆到 badEffects,两边都要给
|
||||
List<String[]> all = new ArrayList<>(ac.giveEffects);
|
||||
if (ac.badEffects != null) all.addAll(ac.badEffects);
|
||||
for (String[] parts : all) {
|
||||
String effName = parts[0];
|
||||
int level = 1;
|
||||
int duration = 999;
|
||||
@@ -7201,6 +7287,41 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
plugin.areaCLIManager.startEditGiveEffect(p, args[2], args[3], args[4]);
|
||||
break;
|
||||
case "effectsbadremove":
|
||||
// ★ 移除指定负面效果(2026-10-04 从增益拆出)
|
||||
if (args.length < 4) {
|
||||
p.sendMessage("§c用法: /protect cli effectsbadremove <领地名> <序号>");
|
||||
break;
|
||||
}
|
||||
int badIdx = 1;
|
||||
try { badIdx = Integer.parseInt(args[3]); } catch (Exception ignored) {}
|
||||
plugin.areaCLIManager.removeBadEffect(p, args[2], badIdx);
|
||||
// 刷新:回到负面效果列表(subPage 6)
|
||||
plugin.areaCLIManager.showEffectsManagement(p, args[2], 6);
|
||||
break;
|
||||
case "effectsbadadd":
|
||||
// ★ 添加负面效果: 效果名 [等级] [秒数]
|
||||
if (args.length < 4) {
|
||||
p.sendMessage("§c用法: /protect cli effectsbadadd <领地名> <效果名> [等级] [秒数]");
|
||||
break;
|
||||
}
|
||||
String badEffName = args[3];
|
||||
String badEffLevel = "1";
|
||||
String badEffDuration = "999";
|
||||
if (args.length >= 5) badEffLevel = args[4];
|
||||
if (args.length >= 6) badEffDuration = args[5];
|
||||
plugin.areaCLIManager.addBadEffect(p, args[2], badEffName, badEffLevel, badEffDuration);
|
||||
// 刷新:回到负面效果列表(subPage 6)
|
||||
plugin.areaCLIManager.showEffectsManagement(p, args[2], 6);
|
||||
break;
|
||||
case "effectsbadedit":
|
||||
// ★ 编辑负面效果等级或时长: 序号 level|duration
|
||||
if (args.length < 5) {
|
||||
p.sendMessage("§c用法: /protect cli effectsbadedit <领地主> <序号> <level|duration>");
|
||||
break;
|
||||
}
|
||||
plugin.areaCLIManager.startEditBadEffect(p, args[2], args[3], args[4]);
|
||||
break;
|
||||
case "announcement":
|
||||
// ★ 领地公告管理
|
||||
if (args.length < 3) {
|
||||
@@ -10337,8 +10458,8 @@ public class AreaProtection implements Listener {
|
||||
+ "confiscate_msg, enable_announce, announce_template, txt_content, created_at, "
|
||||
+ "deny_thrown_projectiles, deny_glowing, deny_redstone_interaction, deny_door_interaction, "
|
||||
+ "deny_noteblock_jukebox, deny_lead, deny_crop_harvest, deny_wool_shear, deny_animal_feeding, "
|
||||
+ "warp_x, warp_y, warp_z, warp_yaw, warp_pitch, warp_world, deny_container, deny_mob_attack, deny_sign_edit, is_public_building, allow_visitor_teleport, deny_farmland_trample, deny_ender_teleport, create_cost, deny_spawn_egg, deny_wax) "
|
||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
+ "warp_x, warp_y, warp_z, warp_yaw, warp_pitch, warp_world, deny_container, deny_mob_attack, deny_sign_edit, is_public_building, allow_visitor_teleport, deny_farmland_trample, deny_ender_teleport, create_cost, deny_spawn_egg, deny_wax, bad_effects) "
|
||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
+ "ON CONFLICT(name) DO UPDATE SET "
|
||||
+ "owner=excluded.owner, world=excluded.world, x1=excluded.x1, z1=excluded.z1, x2=excluded.x2, z2=excluded.z2, y_min=excluded.y_min, y_max=excluded.y_max, "
|
||||
+ "confiscate_items=excluded.confiscate_items, deny_use_items=excluded.deny_use_items, give_effects=excluded.give_effects, clear_effects=excluded.clear_effects, clear_all_bad=excluded.clear_all_bad, "
|
||||
@@ -10351,7 +10472,7 @@ public class AreaProtection implements Listener {
|
||||
+ "confiscate_msg=excluded.confiscate_msg, enable_announce=excluded.enable_announce, announce_template=excluded.announce_template, txt_content=excluded.txt_content, created_at=excluded.created_at, "
|
||||
+ "deny_thrown_projectiles=excluded.deny_thrown_projectiles, deny_glowing=excluded.deny_glowing, deny_redstone_interaction=excluded.deny_redstone_interaction, deny_door_interaction=excluded.deny_door_interaction, "
|
||||
+ "deny_noteblock_jukebox=excluded.deny_noteblock_jukebox, deny_lead=excluded.deny_lead, deny_crop_harvest=excluded.deny_crop_harvest, deny_wool_shear=excluded.deny_wool_shear, deny_animal_feeding=excluded.deny_animal_feeding, "
|
||||
+ "warp_x=excluded.warp_x, warp_y=excluded.warp_y, warp_z=excluded.warp_z, warp_yaw=excluded.warp_yaw, warp_pitch=excluded.warp_pitch, warp_world=excluded.warp_world, deny_container=excluded.deny_container, deny_mob_attack=excluded.deny_mob_attack, deny_sign_edit=excluded.deny_sign_edit, is_public_building=excluded.is_public_building, allow_visitor_teleport=excluded.allow_visitor_teleport, deny_farmland_trample=excluded.deny_farmland_trample, deny_ender_teleport=excluded.deny_ender_teleport, create_cost=excluded.create_cost, deny_spawn_egg=excluded.deny_spawn_egg, deny_wax=excluded.deny_wax");
|
||||
+ "warp_x=excluded.warp_x, warp_y=excluded.warp_y, warp_z=excluded.warp_z, warp_yaw=excluded.warp_yaw, warp_pitch=excluded.warp_pitch, warp_world=excluded.warp_world, deny_container=excluded.deny_container, deny_mob_attack=excluded.deny_mob_attack, deny_sign_edit=excluded.deny_sign_edit, is_public_building=excluded.is_public_building, allow_visitor_teleport=excluded.allow_visitor_teleport, deny_farmland_trample=excluded.deny_farmland_trample, deny_ender_teleport=excluded.deny_ender_teleport, create_cost=excluded.create_cost, deny_spawn_egg=excluded.deny_spawn_egg, deny_wax=excluded.deny_wax, bad_effects=excluded.bad_effects");
|
||||
|
||||
stmt.setString(1, ac.name);
|
||||
stmt.setString(2, ac.owner != null ? ac.owner : "");
|
||||
@@ -10425,6 +10546,7 @@ public class AreaProtection implements Listener {
|
||||
stmt.setInt(70, ac.createCost);
|
||||
stmt.setInt(71, ac.denySpawnEgg ? 1 : 0);
|
||||
stmt.setInt(72, ac.denyWax ? 1 : 0);
|
||||
stmt.setString(73, effectsToString(ac.badEffects));
|
||||
stmt.executeUpdate();
|
||||
stmt.close();
|
||||
// ★ 领地设置变更:立即触发PHP同步(防抖10秒)
|
||||
@@ -10633,7 +10755,7 @@ public class AreaProtection implements Listener {
|
||||
try {
|
||||
// 白名单校验字段名(防SQL注入)
|
||||
java.util.Set<String> allowedFields = new java.util.HashSet<>(java.util.Arrays.asList(
|
||||
"give_effects", "clear_effects", "clear_all_bad", "deny_all_effects",
|
||||
"give_effects", "bad_effects", "clear_effects", "clear_all_bad", "deny_all_effects",
|
||||
"deny_block_place", "deny_block_break", "deny_pvp", "deny_fall_damage",
|
||||
"deny_hunger", "deny_all_damage", "deny_drop", "deny_mount", "deny_ender_pearl",
|
||||
"deny_bow", "deny_potion", "deny_explosion", "deny_raid", "deny_fire_spread",
|
||||
@@ -10852,22 +10974,26 @@ public class AreaProtection implements Listener {
|
||||
case "confiscate_msg": ac.confiscateMsg = value; break;
|
||||
case "enforce_game_mode": ac.enforceGameMode = value; break;
|
||||
// 复合字段(JSON 数组或管道格式 → List)
|
||||
// ★ 2026-10-04:负面效果拆到 bad_effects,PHP 推来的字段名同样是 bad_effects
|
||||
case "give_effects":
|
||||
ac.giveEffects = parseEffectsString(value);
|
||||
// ★ 2026-10-04 任务3:数量上限截断 + 等级原版封顶(1~255)
|
||||
case "bad_effects": {
|
||||
boolean isBad = "bad_effects".equals(field);
|
||||
List<String[]> parsed = parseEffectsString(value);
|
||||
// ★ 数量上限截断:增益与负面共用同一份 max_effects 预算(按合计算)
|
||||
try {
|
||||
int maxEff = getMaxGiveEffects(ac.owner);
|
||||
if (ac.giveEffects.size() > maxEff) {
|
||||
int otherCount = isBad ? ac.giveEffects.size() : ac.badEffects.size();
|
||||
int room = Math.max(0, maxEff - otherCount);
|
||||
if (parsed.size() > room) {
|
||||
plugin.getLogger().warning("[防护] 领地 "
|
||||
+ ac.name + " 增益效果 "
|
||||
+ ac.giveEffects.size() + " 个超出上限 "
|
||||
+ maxEff + ",已截断保留前 "
|
||||
+ maxEff + " 个");
|
||||
ac.giveEffects = new java.util.ArrayList<>(
|
||||
ac.giveEffects.subList(0, maxEff));
|
||||
+ ac.name + (isBad ? " 负面效果 " : " 增益效果 ")
|
||||
+ parsed.size() + " 个超出上限 "
|
||||
+ maxEff + "(另一类已占 " + otherCount
|
||||
+ "),已截断保留前 " + room + " 个");
|
||||
parsed = new java.util.ArrayList<>(parsed.subList(0, room));
|
||||
}
|
||||
// ★ 药效等级封顶(2026-10-04 任务4):逐个效果按「生存可获取上限 + 用户组配置」
|
||||
for (String[] ef : ac.giveEffects) {
|
||||
for (String[] ef : parsed) {
|
||||
if (ef.length >= 2) {
|
||||
try {
|
||||
int lv = Integer.parseInt(ef[1]);
|
||||
@@ -10881,10 +11007,16 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().warning("[防护] give_effects 上限校验异常: "
|
||||
plugin.getLogger().warning("[防护] " + field + " 上限校验异常: "
|
||||
+ ex.getMessage());
|
||||
}
|
||||
if (isBad) {
|
||||
ac.badEffects = parsed;
|
||||
} else {
|
||||
ac.giveEffects = parsed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "clear_effects":
|
||||
ac.clearEffects.clear();
|
||||
if (value != null && !value.isEmpty()) {
|
||||
@@ -11371,10 +11503,11 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
}
|
||||
// ★ 数量上限(2026-10-04 任务3):普通玩家 5 个,用户组可覆盖
|
||||
// 2026-10-04 负面拆出后,增益与负面共用同一份 max_effects 预算,故按合计判
|
||||
int maxEffCnt = getMaxGiveEffects(land.owner);
|
||||
if (land.giveEffects.size() >= maxEffCnt) {
|
||||
p.sendMessage("§c§l[添加增益效果] §f该领地增益效果已达上限 §e"
|
||||
+ maxEffCnt + " §f个(按用户组配置),请先移除再添加");
|
||||
if (land.giveEffects.size() + land.badEffects.size() >= maxEffCnt) {
|
||||
p.sendMessage("§c§l[添加增益效果] §f效果已达上限 §e"
|
||||
+ maxEffCnt + " §f个(增益+负面合计,按用户组配置),请先移除再添加");
|
||||
return true;
|
||||
}
|
||||
String[] effRecord = {effName, String.valueOf(effLv), String.valueOf(effDur)};
|
||||
@@ -11428,6 +11561,54 @@ public class AreaProtection implements Listener {
|
||||
}
|
||||
saveAreaToDb(land);
|
||||
if (plugin.areaCLIManager != null) plugin.areaCLIManager.showEffectsManagement(p, landName, 3);
|
||||
} else if (inputType.startsWith("editBad_")) {
|
||||
// ★ 编辑负面效果等级/时长: editBad_level_3 或 editBad_duration_3
|
||||
// (2026-10-04 负面从增益拆出,回写落到 badEffects 而不是 giveEffects)
|
||||
String[] parts = inputType.split("_");
|
||||
if (parts.length < 3) return true;
|
||||
String editField = parts[1]; // level 或 duration
|
||||
int idx;
|
||||
try { idx = Integer.parseInt(parts[2]); } catch (Exception e) { return true; }
|
||||
if (idx < 1 || idx > land.badEffects.size()) {
|
||||
p.sendMessage("§c序号超出范围");
|
||||
return true;
|
||||
}
|
||||
if (message.trim().equalsIgnoreCase("取消")) {
|
||||
p.sendMessage("§7已取消编辑");
|
||||
if (plugin.areaCLIManager != null) plugin.areaCLIManager.showEffectsManagement(p, landName, 6);
|
||||
return true;
|
||||
}
|
||||
String[] eff = land.badEffects.get(idx - 1);
|
||||
if ("level".equals(editField)) {
|
||||
int newLevel;
|
||||
try { newLevel = Integer.parseInt(message.trim()); } catch (Exception e) {
|
||||
p.sendMessage("§c请输入有效数字");
|
||||
return true;
|
||||
}
|
||||
int maxLvEdit = getEffectLevelCap(eff[0], land.owner);
|
||||
if (newLevel < 1 || newLevel > maxLvEdit) {
|
||||
p.sendMessage("§c等级范围 1~" + maxLvEdit);
|
||||
p.sendMessage("§7上限说明: " + eff[0] + " 生存模式最高可获得 "
|
||||
+ getSurvivalMaxLevel(eff[0]) + " 级");
|
||||
p.sendMessage("§7你当前领地可用上限 " + maxLvEdit + " 级(受用户组配置约束)");
|
||||
return true;
|
||||
}
|
||||
if (eff.length < 2) { String[] tmp = new String[3]; System.arraycopy(eff, 0, tmp, 0, eff.length); eff = tmp; land.badEffects.set(idx - 1, eff); }
|
||||
eff[1] = String.valueOf(newLevel);
|
||||
p.sendMessage("§c§l[编辑负面] §f已将 §e" + eff[0] + " §f等级修改为 " + newLevel);
|
||||
} else {
|
||||
int newDur;
|
||||
try { newDur = Integer.parseInt(message.trim()); } catch (Exception e) {
|
||||
p.sendMessage("§c请输入有效数字");
|
||||
return true;
|
||||
}
|
||||
if (newDur < 1 || newDur > 86400) { p.sendMessage("§c时长范围1~86400秒"); return true; }
|
||||
if (eff.length < 3) { String[] tmp = new String[3]; System.arraycopy(eff, 0, tmp, 0, Math.min(eff.length, 3)); eff = tmp; land.badEffects.set(idx - 1, eff); }
|
||||
eff[2] = String.valueOf(newDur);
|
||||
p.sendMessage("§c§l[编辑负面] §f已将 §e" + eff[0] + " §f时长修改为 " + newDur + "秒");
|
||||
}
|
||||
saveAreaToDb(land);
|
||||
if (plugin.areaCLIManager != null) plugin.areaCLIManager.showEffectsManagement(p, landName, 6);
|
||||
} else if ("rename_land".equals(inputType)) {
|
||||
// ★ 领地改名输入处理
|
||||
if (message.trim().equals("取消") || message.trim().equals("0")) {
|
||||
@@ -13050,6 +13231,14 @@ public class AreaProtection implements Listener {
|
||||
public List<String> clearEffects = new ArrayList<>(); // 清除单个效果名
|
||||
public boolean clearAllBadEffects = false; // 清除所有负面+中性
|
||||
public List<String[]> giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数]
|
||||
/**
|
||||
* ★ 负面效果独立列表(2026-10-04 用户要求从增益里拆出来)
|
||||
*
|
||||
* <p>结构同 {@link #giveEffects}:[效果名, 等级, 秒数]。原实现把所有效果
|
||||
* (含缓慢/中毒/凋零等负面)全塞在 {@code giveEffects} 里,GUI 上也只能一把选,
|
||||
* 负面与正面混排。拆开后 GUI 分两个子菜单、CLI 分两套指令、上限口径也各归各。</p>
|
||||
*/
|
||||
public List<String[]> badEffects = new ArrayList<>(); // [效果名, 等级, 秒数]
|
||||
public List<String> punishCommands = new ArrayList<>();
|
||||
public boolean denyBlockPlace = false;
|
||||
public boolean denyBlockBreak = false;
|
||||
|
||||
@@ -187,6 +187,8 @@ public class Main extends JavaPlugin
|
||||
public AreaProtection areaProtection;
|
||||
public AreaGUIManager areaGUIManager;
|
||||
public AreaCLIManager areaCLIManager;
|
||||
/** ★ 药水类成就防作弊(2026-10-04):插件给的效果一律打标记,靠插件拿到的原版成就立即撤销 */
|
||||
public AchievementEffectGuard achievementEffectGuard;
|
||||
public LandRecordManager landRecordManager;
|
||||
public InventoryAutoBackup inventoryAutoBackup;
|
||||
private TeleportManager teleportMgr;
|
||||
@@ -775,6 +777,13 @@ public class Main extends JavaPlugin
|
||||
// 配置文件 plugins/Sdf1_login/白名单.txt,登录/加入/退出时热重载
|
||||
getServer().getPluginManager().registerEvents(new MaintenanceManager(this), this);
|
||||
getCommand("back").setTabCompleter(quickBack);
|
||||
|
||||
// ===== 药水类成就防作弊 =====
|
||||
// 插件给的效果必须打标记;玩家靠插件效果凑齐 nether/all_potions(17种) 或
|
||||
// nether/all_effects(34种) 时,服务端立即撤销该成就——这两条必须手打。
|
||||
achievementEffectGuard = new AchievementEffectGuard(this);
|
||||
getServer().getPluginManager().registerEvents(achievementEffectGuard, this);
|
||||
getLogger().info("[成就防作弊] 已启用:受管控成就 nether/all_potions、nether/all_effects");
|
||||
|
||||
// 注册传送命令已在 onCommand 中通过条件分支完成
|
||||
|
||||
|
||||
@@ -3881,6 +3881,8 @@ public class WebManager {
|
||||
hashBuilder.append(land.getOrDefault("mode_exempt", "")).append(":");
|
||||
hashBuilder.append(land.getOrDefault("clear_effects", "")).append(":");
|
||||
hashBuilder.append(land.getOrDefault("give_effects", "")).append(":");
|
||||
// ★ 2026-10-04:负面效果拆出后也要进变更哈希,否则改了负面不会触发同步
|
||||
hashBuilder.append(land.getOrDefault("bad_effects", "")).append(":");
|
||||
hashBuilder.append(land.getOrDefault("clear_all_bad", 0)).append(":");
|
||||
// 传送点
|
||||
hashBuilder.append(land.getOrDefault("warp_x", 0)).append(":");
|
||||
|
||||
Reference in New Issue
Block a user