修复PVP加入卡服(13s看门狗)并新增公共设施连控权限

- PVP世界改由 WorldType.FLAT 空世界+手动地形生成:createWorld 毫秒级返回,
  彻底杜绝主线程13s卡服与 Chunk wait 看门狗报错;随机种子仍保证每次地形不同(防背图);
  新增 forceSafeSpawn 固化安全出生点,绕开原'水→删除重建换种子'卡服链路
- 公共设施连控:领地 isPublicBuilding 启用时,所有访客自动获得 传送/容器交互/攻击敌对生物
  3项权限(传送在 canTeleportToLand,容器交互在两处 denyContainer 检查,攻击敌对生物复用既有逻辑)
This commit is contained in:
2026-07-08 20:58:41 +08:00
parent ee22930efd
commit f4c5c0049a
2 changed files with 52 additions and 20 deletions
@@ -2268,6 +2268,8 @@ public class AreaProtection implements Listener {
if (isAreaAdmin(p)) return true;
if (p.getName().equalsIgnoreCase(ac.owner)) return true;
if (isLandAdmin(ac.name, p.getName())) return true;
// ★ 公共设施连控:公共设施启用时,所有访客自动获得传送权限(优先于 per-player 检查)
if (ac.isPublicBuilding) return true;
// ★ 访客传送权限:领地级别允许则放行(优先于per-player检查)
if (ac.allowVisitorTeleport) return true;
int landId = getLandIdFromDb(ac.name);
@@ -4538,6 +4540,8 @@ public class AreaProtection implements Listener {
if (clicked != null) {
Material clickedMat = clicked.getType();
if (isUIBlock(clickedMat)) {
// ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(工作台/砂轮等UI方块同样放开)
if (ac.isPublicBuilding) return;
if (getEffectiveDeny(p, ac, "denyContainer")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f此领地禁止访问容器");
@@ -4684,6 +4688,9 @@ public class AreaProtection implements Listener {
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
// ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(跳过禁止检查)
if (ac.isPublicBuilding) return;
// ★ 容器管理权限:denyContainer=true时非领主/管理员不能访问容器
if (getEffectiveDeny(p, ac, "denyContainer")) {
e.setCancelled(true);
+45 -20
View File
@@ -262,11 +262,11 @@ public class PVPArenaManager implements Listener {
* 设计:玩家全部离开后世界被删除,下次加入时重新随机种子生成【全新地形】,
* 避免熟悉地图的玩家背图追杀新手。
*
* 出生点合法性:脚下方块必须是【实体方块】(固体)才使用;若随机到水/空气等非实体方块,
* 则删除该世界、换一个新种子重新生成(极少数情况连续多次都抽到水出生点,会重试但概率极低)。
*
* 卡服修复:Bukkit.createWorld 仅做世界初始化(不强制加载出生点区块),
* 主线程毫秒级返回;出生点周边区块由 preGenerateSpawnChunksAsync 异步预生成,不阻塞主线程。
* ★ 卡服根因修复(2026-07-08):原实现用 WorldType.NORMAL + Bukkit.createWorld 在主线程
* 同步"搜索出生点 + 生成自然地形",耗时 13s 触发 Paper 看门狗(Chunk wait)导致卡服/报错。
* 现改为 WorldType.FLAT 空世界:createWorld 毫秒级返回(无地形搜索),随后用
* generatePVPTerrain 按【随机种子】手动生成自然地形(每次丘陵/水域/树木分布不同 → 仍防背图),
* 最后 forceSafeSpawn 固化一个绝对安全的出生点,彻底绕开"水→删除重建换种子"的卡服链路。
*/
private World createPVPWorld() {
long t0 = System.currentTimeMillis();
@@ -278,12 +278,12 @@ public class PVPArenaManager implements Listener {
long seed = worldSeedRandom.nextLong();
WorldCreator c = new WorldCreator(pvpWorldName);
c.environment(World.Environment.NORMAL);
c.type(WorldType.NORMAL);
// ★ 关键:FLAT 空世界使 createWorld 在主线程毫秒级返回,杜绝 13s 卡服;地形后续手动生成
c.type(WorldType.FLAT);
c.seed(seed);
// 注意:Paper 26.x 已移除 WorldCreator.keepSpawnInMemory(boolean),不再调用
// 区块加载控制改为 createWorld 后通过 world.setKeepLoaded(false) 实现
plugin.getLogger().info("[PVP] 生成PVP竞技场世界 " + pvpWorldName + " 种子=" + seed);
plugin.getLogger().info("[PVP] 生成PVP竞技场世界 " + pvpWorldName + " 种子=" + seed + " (FLAT空世界,随后手动生成地形)");
World pvpWorld = Bukkit.createWorld(c);
long dt = System.currentTimeMillis() - t0;
if (pvpWorld == null) {
@@ -291,24 +291,49 @@ public class PVPArenaManager implements Listener {
return null;
}
// ★ 出生点脚下方块检查:实体(固体)方块→使用;水/空气等非实体→删除重来(换新种子)
Location spawn = pvpWorld.getSpawnLocation();
Block feet = pvpWorld.getBlockAt(spawn.getBlockX(), spawn.getBlockY() - 1, spawn.getBlockZ());
if (!isSolidBlock(feet.getType())) {
plugin.getLogger().warning("[PVP] 种子 " + seed + " 出生点为非实体方块(" + feet.getType().name()
+ "),删除世界换种子重来");
deletePVPWorld(pvpWorld);
return createPVPWorld(); // 递归重试(新随机种子)
}
// 出生点合格:异步预生成周边区块(不卡主线程),同时应用世界规则
// ★ 手动生成自然地形(随机种子→每次地形不同,防背图),同步完成(仅96x96,远低于13s)
generatePVPTerrain(pvpWorld, seed);
// ★ 固化安全出生点:中心附近找实体非流体方块,颠覆"水→重建"的卡服链路
forceSafeSpawn(pvpWorld);
// 异步预生成周边区块(兜底,不阻塞主线程)
preGenerateSpawnChunksAsync(pvpWorld, 4);
reapplyWorldRules(pvpWorld);
plugin.getLogger().info("[PVP] PVP竞技场世界就绪 种子=" + seed + " 耗时=" + dt + "ms(出生点为实体方块)");
plugin.getLogger().info("[PVP] PVP竞技场世界就绪 种子=" + seed + " 耗时=" + dt + "ms(FLAT+手动地形,出生点已固化)");
return pvpWorld;
}
/**
* 固化PVP世界出生点:在中心半径内寻找第一个"实体且非流体"的方块作为出生点。
* 找不到则强制在中心 (0,24,0) 搭建安全平台。
* 目的:彻底规避原 NORMAL 世界"出生点为水→删除重建换种子"导致的 13s 主线程卡服。
*/
private void forceSafeSpawn(World world) {
int maxR = 8;
for (int rad = 0; rad <= maxR; rad++) {
for (int dx = -rad; dx <= rad; dx++) {
for (int dz = -rad; dz <= rad; dz++) {
if (rad > 0 && Math.abs(dx) != rad && Math.abs(dz) != rad) continue; // 仅遍历当前环
int top = getTopSolidY(world, dx, dz);
Material m = world.getBlockAt(dx, top, dz).getType();
if (m.isSolid() && !isFluid(m)) {
world.setSpawnLocation(dx, top + 1, dz);
return;
}
}
}
}
// 兜底:中心安全平台
int sy = 24;
for (int y = 0; y <= sy; y++) {
Block b = world.getBlockAt(0, y, 0);
b.setType(y == sy ? Material.GRASS_BLOCK : (y == 0 ? Material.BEDROCK : Material.STONE));
}
for (int y = sy + 1; y <= sy + 4; y++) world.getBlockAt(0, y, 0).setType(Material.AIR);
world.setSpawnLocation(0, sy + 1, 0);
plugin.getLogger().warning("[PVP] 未找到天然安全出生点,已生成中心兜底平台");
}
/** 是否为实体(固体)方块:玩家可站立的地面。水/空气/岩浆等非固体返回 false */
private boolean isSolidBlock(Material m) {
if (m == null || m.isAir() || m == Material.WATER || m == Material.LAVA) return false;