修复领地增益药效等级无上限,新增用户组药效等级上限配置
Check Source / check (push) Successful in 3s
Auto Release / build (push) Successful in 2m32s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 1s

问题:领地增益效果的等级完全没校验,管理员配了 255 级就会原样
生效(截图里出现的 Lv255 即源于此)。原版 /effect 的 amplifier 硬
上限是 255(对应药效强度 1~256),插件此前允许写入并施加远超原版
范围的等级。

修复(2026-10-04 任务4):
- UserGroupManager 新增 maxEffectLevel 字段,默认 256
  · 常量 DEFAULT_MAX_EFFECT_LEVEL = 256 集中定义原版上限
  · user_group_config 建表补列 + ALTER 迁移(老库自动升级)
  · loadGroupConfigs 读取、saveGroupConfigToDB 加为第 14 列
  · 新增 getPlayerMaxEffectLevel:普通玩家走原版 256,用户组按组配置
- AreaProtection 新增 getMaxGiveEffectLevel / clampGiveEffectLevel,
  在全部 4 个应用点封顶:首次给予(applyRegionEffects)、热更新、
  handleEnter、applyEffects;另在 PHP 字段推送(applyFieldToAreaConfig)
  与 CLI 添加/编辑两处入口按同一上限做输入校验与提示
- WebManager 用户组同步双向补 max_effect_level(推 PHP + 回读)

规则:普通玩家(未加入任何用户组)严格按原版 256 封顶,严禁超限;
用户组玩家按组配置封顶,组里配多少就是多少,但任何情况下都不得
突破原版 256。原先散落各处的硬编码 255 已统一为该口径。
This commit is contained in:
youpaishidifu
2026-10-04 19:25:39 +08:00
parent f2808a5923
commit 6f9202859d
3 changed files with 105 additions and 10 deletions
+55 -7
View File
@@ -3825,7 +3825,8 @@ public class AreaProtection implements Listener {
PotionEffectType type =
resolveEffectType(eff[0]);
if (type == null) continue;
int lv = Integer.parseInt(eff[1]) - 1;
// ★ 药效等级封顶(2026-10-04 任务4):普通玩家按原版256,用户组按组配置
int lv = clampGiveEffectLevel(Integer.parseInt(eff[1]), ac.owner) - 1;
int dur = Integer.parseInt(eff[2]) * 20;
PotionEffect existing =
@@ -3891,7 +3892,8 @@ public class AreaProtection implements Listener {
PotionEffectType type = resolveEffectType(eff[0]);
if (type == null) continue;
try {
int lv = Integer.parseInt(eff[1]) - 1; // MC amplifier从0开始
// ★ 药效等级封顶(2026-10-04 任务4),与首次给予同一口径
int lv = clampGiveEffectLevel(Integer.parseInt(eff[1]), ac.owner) - 1; // MC amplifier从0开始
int dur = Integer.parseInt(eff[2]) * 20; // 秒→tick
expectedEffects.put(type, new int[]{lv, dur});
} catch (NumberFormatException ignored) {}
@@ -4162,6 +4164,8 @@ public class AreaProtection implements Listener {
catch (NumberFormatException ignored) {}
try { duration = Integer.parseInt(parts[2]); }
catch (NumberFormatException ignored) {}
// ★ 药效等级封顶(2026-10-04 任务4)
level = clampGiveEffectLevel(level, ac.owner);
PotionEffectType type = resolveEffectType(effName);
if (type != null) {
p.addPotionEffect(new PotionEffect(
@@ -4912,6 +4916,39 @@ public class AreaProtection implements Listener {
return 5;
}
/**
* ★ 领地增益【药效等级上限】(2026-10-04 任务4)。
* 普通玩家(无用户组)严格按原版最大药效强度 256 封顶;
* 用户组玩家按组配置 max_effect_level,组里配多少就是多少。
*
* @param ownerName 领地所有者(上限跟随领地所有者所属用户组,与数量上限一致口径)
* @return 药效强度上限,恒在 1~256 之间
*/
public int getMaxGiveEffectLevel(String ownerName) {
try {
UserGroupManager ugm = plugin.getUserGroup();
if (ugm != null) {
return ugm.getPlayerMaxEffectLevel(ownerName,
UserGroupManager.DEFAULT_MAX_EFFECT_LEVEL);
}
} catch (Exception ignored) {}
return UserGroupManager.DEFAULT_MAX_EFFECT_LEVEL;
}
/**
* 把领地配置里的药效等级夹到该领地的合法上限内。
* 所有写入/应用入口都必须过这一刀。
*
* @param rawLevel 配置里写的原始药效强度
* @param ownerName 领地所有者
* @return 夹紧后的药效强度(1~256,且 <= 该领地用户组上限)
*/
public int clampGiveEffectLevel(int rawLevel, String ownerName) {
int cap = getMaxGiveEffectLevel(ownerName);
if (rawLevel < 1) return 1;
return rawLevel > cap ? cap : rawLevel;
}
public List<String> getUserGroupNames() {
List<String> names = new ArrayList<>();
UserGroupManager ugm = plugin.getUserGroup();
@@ -5111,6 +5148,8 @@ public class AreaProtection implements Listener {
catch (NumberFormatException ignored) {}
try { duration = Integer.parseInt(parts[2]); }
catch (NumberFormatException ignored) {}
// ★ 药效等级封顶(2026-10-04 任务4)
level = clampGiveEffectLevel(level, ac.owner);
PotionEffectType type = resolveEffectType(effName);
plugin.getLogger().info("[防护调试] 给予: "
@@ -10696,11 +10735,13 @@ public class AreaProtection implements Listener {
ac.giveEffects = new java.util.ArrayList<>(
ac.giveEffects.subList(0, maxEff));
}
// ★ 药效等级按该领地用户组上限封顶(2026-10-04 任务4)
int maxLv = getMaxGiveEffectLevel(ac.owner);
for (String[] ef : ac.giveEffects) {
if (ef.length >= 2) {
try {
int lv = Integer.parseInt(ef[1]);
if (lv > 255) ef[1] = "255";
if (lv > maxLv) ef[1] = String.valueOf(maxLv);
else if (lv < 1) ef[1] = "1";
} catch (NumberFormatException ignored) {}
}
@@ -11169,9 +11210,11 @@ public class AreaProtection implements Listener {
p.sendMessage("§c§l[添加增益效果] §f等级和秒数必须是数字");
return true;
}
// ★ 验证等级和秒数范围
if (effLv < 1 || effLv > 255) {
p.sendMessage("§c§l[添加增益效果] §f等级范围: 1~255");
// ★ 验证等级和秒数范围(等级上限按用户组配置,2026-10-04 任务4)
int maxLvAdd = getMaxGiveEffectLevel(land.owner);
if (effLv < 1 || effLv > maxLvAdd) {
p.sendMessage("§c§l[添加增益效果] §f等级范围: 1~" + maxLvAdd);
p.sendMessage("§7上限说明: 普通玩家为原版上限 256,你当前可用的上限是 " + maxLvAdd);
return true;
}
if (effDur < 1 || effDur > 3600) {
@@ -11226,7 +11269,12 @@ public class AreaProtection implements Listener {
p.sendMessage("§c请输入有效数字");
return true;
}
if (newLevel < 1 || newLevel > 255) { p.sendMessage("§c等级范围1~255"); return true; }
int maxLvEdit = getMaxGiveEffectLevel(land.owner);
if (newLevel < 1 || newLevel > maxLvEdit) {
p.sendMessage("§c等级范围1~" + maxLvEdit);
p.sendMessage("§7上限说明: 普通玩家为原版上限 256,你当前可用的上限是 " + maxLvEdit);
return true;
}
if (eff.length < 2) { String[] tmp = new String[3]; System.arraycopy(eff, 0, tmp, 0, eff.length); eff = tmp; land.giveEffects.set(idx - 1, eff); }
eff[1] = String.valueOf(newLevel);
p.sendMessage("§a§l[编辑增益] §f已将 §e" + eff[0] + " §f等级修改为 " + newLevel);
+42 -3
View File
@@ -52,7 +52,8 @@ public class UserGroupManager {
+ "max_lands INTEGER DEFAULT -1," // -1 = 使用全局值
+ "default_perms TEXT DEFAULT '{}'," // JSON: 默认deny_*标志
+ "is_permanent INTEGER DEFAULT 1," // 保留兼容
+ "duration_minutes INTEGER DEFAULT 0" // 保留兼容
+ "duration_minutes INTEGER DEFAULT 0," // 保留兼容
+ "max_effect_level INTEGER DEFAULT 256" // 药效等级上限,<=0=按原版256
+ ")");
// 玩家↔用户组关联
@@ -78,6 +79,8 @@ public class UserGroupManager {
try { st.executeUpdate("ALTER TABLE user_group_config ADD COLUMN renew_price INTEGER DEFAULT 0"); } catch (Exception ignored) {}
// ★ 领地增益效果上限(2026-10-04 任务3):默认5个,<=0=不限
try { st.executeUpdate("ALTER TABLE user_group_config ADD COLUMN max_effects INTEGER DEFAULT 5"); } catch (Exception ignored) {}
// ★ 药效等级上限(2026-10-04 任务4):默认256=原版最大药效强度,<=0=按原版
try { st.executeUpdate("ALTER TABLE user_group_config ADD COLUMN max_effect_level INTEGER DEFAULT 256"); } catch (Exception ignored) {}
// duration_minutes已有(旧列),复用
st.close();
@@ -110,6 +113,8 @@ public class UserGroupManager {
cfg.durationMinutes = rs.getLong("duration_minutes");
cfg.maxEffects = rs.getInt("max_effects");
if (rs.wasNull()) cfg.maxEffects = 5;
cfg.maxEffectLevel = rs.getInt("max_effect_level");
if (rs.wasNull()) cfg.maxEffectLevel = UserGroupManager.DEFAULT_MAX_EFFECT_LEVEL;
if (cfg.name != null && !cfg.name.isEmpty()) {
groupConfigs.put(cfg.name, cfg);
}
@@ -131,8 +136,8 @@ public class UserGroupManager {
+ "(group_name, display_name, display_color,"
+ " priority, land_price_per_sqm, max_lands, default_perms,"
+ " home_limit, join_price, auto_renew, renew_price, duration_minutes,"
+ " max_effects)"
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
+ " max_effects, max_effect_level)"
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
ps.setString(1, cfg.name);
ps.setString(2, cfg.displayName);
ps.setString(3, cfg.displayColor);
@@ -146,6 +151,7 @@ public class UserGroupManager {
ps.setInt(11, cfg.renewPrice);
ps.setLong(12, cfg.durationMinutes);
ps.setInt(13, cfg.maxEffects);
ps.setInt(14, cfg.maxEffectLevel);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
@@ -980,6 +986,30 @@ public class UserGroupManager {
return cfg.maxEffects;
}
/**
* ★ 药效等级上限(2026-10-04 任务4)。
* 普通玩家(无用户组)严格按原版最大药效强度 256 封顶;
* 用户组玩家按组配置 max_effect_level,配置多少就是多少。
* 组配置 <=0 视为「不额外限制」,仍受原版 256 硬上限约束。
*
* @param player 玩家名
* @param globalDefault 无组时的全局默认(原版上限 256)
* @return 药效强度上限,恒 >=1 且 <=256
*/
public int getPlayerMaxEffectLevel(String player, int globalDefault) {
int cap = DEFAULT_MAX_EFFECT_LEVEL;
if (globalDefault > 0 && globalDefault < cap) {
cap = globalDefault;
}
if (player != null && !player.isEmpty()) {
UserGroupConfig cfg = getHighestGroup(player);
if (cfg != null && cfg.maxEffectLevel > 0 && cfg.maxEffectLevel < cap) {
cap = cfg.maxEffectLevel;
}
}
return cap;
}
/**
* 获取玩家的默认领地权限 JSON
* 返回:组默认权限 或 空JSON "{}"
@@ -1151,6 +1181,14 @@ public class UserGroupManager {
// ==================== 配置类 ====================
/**
* ★ 原版最大药效强度(2026-10-04 任务4)。
* Minecraft 药效强度范围 1~256,amplifier = 强度 - 1,即 0~255,
* /effect 命令的 amplifier 参数硬上限就是 255。
* 普通玩家(无用户组)一律以此为上限,严禁超限。
*/
public static final int DEFAULT_MAX_EFFECT_LEVEL = 256;
public static class UserGroupConfig {
public String name;
public String displayName = "";
@@ -1160,6 +1198,7 @@ public class UserGroupManager {
public int landPricePerSqm = -1; // -1 = 使用全局
public int maxLands = -1; // -1 = 使用全局
public int maxEffects = 5; // 领地增益效果上限,<=0=不限
public int maxEffectLevel = DEFAULT_MAX_EFFECT_LEVEL; // 药效等级上限,<=0=按原版256
public String defaultPerms = "{}"; // JSON
// Home相关
public int homeLimit = 0; // 0=跟随全局, >0=独立限制, -1=无限
+8
View File
@@ -4039,6 +4039,7 @@ public class WebManager {
gs.append("\"land_price_per_sqm\":").append(cfg.landPricePerSqm).append(",");
gs.append("\"max_lands\":").append(cfg.maxLands).append(",");
gs.append("\"max_effects\":").append(cfg.maxEffects).append(",");
gs.append("\"max_effect_level\":").append(cfg.maxEffectLevel).append(",");
gs.append("\"home_limit\":").append(cfg.homeLimit).append(",");
gs.append("\"join_price\":").append(cfg.joinPrice).append(",");
gs.append("\"auto_renew\":").append(cfg.autoRenew ? 1 : 0).append(",");
@@ -4437,6 +4438,13 @@ public class WebManager {
try { cfg.maxEffects = Integer.parseInt(maxEffStr); }
catch (Exception ignored) { cfg.maxEffects = 5; }
}
// ★ 药效等级上限(2026-10-04 任务4)
String maxEffLvStr = extractJsonField(obj, "max_effect_level");
if (!maxEffLvStr.isEmpty()) {
try { cfg.maxEffectLevel = Integer.parseInt(maxEffLvStr); }
catch (Exception ignored) { cfg.maxEffectLevel = UserGroupManager.DEFAULT_MAX_EFFECT_LEVEL; }
}
if (cfg.maxEffectLevel <= 0) cfg.maxEffectLevel = UserGroupManager.DEFAULT_MAX_EFFECT_LEVEL;
String homeLimStr = extractJsonField(obj, "home_limit");
if (!homeLimStr.isEmpty()) try { cfg.homeLimit = Integer.parseInt(homeLimStr); } catch (Exception ignored) {}
String joinStr = extractJsonField(obj, "join_price");